Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Split OMEMO plugin into view and headless components #3117

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/source/plugin_development.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ modular and self-contained way, without having to change other files.

This ensures that plugins are fully optional (one of the design goals of
Converse) and can be removed from the main build without breaking the app.
For example, the ``converse-omemo``,
For example, the ``converse-omemo-views``,
``converse-rosterview``, ``converse-dragresize``, ``converse-minimize``,
``converse-muc`` and ``converse-muc-views`` plugins can all be removed from the
build without breaking the app.
Expand Down
2 changes: 1 addition & 1 deletion src/converse.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import "./plugins/muc-views/index.js"; // Views related to MUC
import "./plugins/minimize/index.js"; // Allows chat boxes to be minimized
import "./plugins/notifications/index.js";
import "./plugins/profile/index.js";
import "./plugins/omemo/index.js";
import "./plugins/omemo-views/index.js";
import "./plugins/push/index.js"; // XEP-0357 Push Notifications
import "./plugins/register/index.js"; // XEP-0077 In-band registration
import "./plugins/roomslist/index.js"; // Show currently open chat rooms
Expand Down
File renamed without changes.
155 changes: 12 additions & 143 deletions src/plugins/omemo/utils.js → src/headless/plugins/omemo/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,11 @@
import concat from 'lodash-es/concat';
import difference from 'lodash-es/difference';
import log from '@converse/headless/log';
import tpl_audio from 'templates/audio.js';
import tpl_file from 'templates/file.js';
import tpl_image from 'templates/image.js';
import tpl_video from 'templates/video.js';
import { KEY_ALGO, UNTRUSTED, TAG_LENGTH } from './consts.js';
import { MIMETYPES_MAP } from 'utils/file.js';
import { __ } from 'i18n';
import { _converse, converse, api } from '@converse/headless/core';
import { html } from 'lit';
import { initStorage } from '@converse/headless/utils/storage.js';
import { isError } from '@converse/headless/utils/core.js';
import { isAudioURL, isImageURL, isVideoURL, getURI } from '@converse/headless/utils/url.js';
import { until } from 'lit/directives/until.js';
import {
appendArrayBuffer,
arrayBufferToBase64,
Expand All @@ -25,7 +17,7 @@ import {
stringToArrayBuffer
} from '@converse/headless/utils/arraybuffer.js';

const { Strophe, URI, sizzle, u } = converse.env;
const { Strophe, sizzle, u } = converse.env;

export function formatFingerprint (fp) {
fp = fp.replace(/^05/, '');
Expand Down Expand Up @@ -197,53 +189,13 @@ async function getAndDecryptFile (uri) {
}
}

function getTemplateForObjectURL (uri, obj_url, richtext) {
if (isError(obj_url)) {
return html`<p class="error">${obj_url.message}</p>`;
}

const file_url = uri.toString();
if (isImageURL(file_url)) {
return tpl_image({
'src': obj_url,
'onClick': richtext.onImgClick,
'onLoad': richtext.onImgLoad
});
} else if (isAudioURL(file_url)) {
return tpl_audio(obj_url);
} else if (isVideoURL(file_url)) {
return tpl_video(obj_url);
} else {
return tpl_file(obj_url, uri.filename());
}

export const omemo = {
decryptMessage,
encryptMessage,
formatFingerprint
}

function addEncryptedFiles(text, offset, richtext) {
const objs = [];
try {
const parse_options = { 'start': /\b(aesgcm:\/\/)/gi };
URI.withinString(
text,
(url, start, end) => {
objs.push({ url, start, end });
return url;
},
parse_options
);
} catch (error) {
log.debug(error);
return;
}
objs.forEach(o => {
const uri = getURI(text.slice(o.start, o.end));
const promise = getAndDecryptFile(uri)
.then(obj_url => getTemplateForObjectURL(uri, obj_url, richtext));

const template = html`${until(promise, '')}`;
richtext.addTemplateResult(o.start + offset, o.end + offset, template);
});
}
// TODO: addEncryptedFiles (without the template part)

export function handleEncryptedFiles (richtext) {
if (!_converse.config.get('trusted')) {
Expand Down Expand Up @@ -306,31 +258,14 @@ export function onChatBoxesInitialized () {
});
}

export function onChatInitialized (el) {
el.listenTo(el.model.messages, 'add', message => {
if (message.get('is_encrypted') && !message.get('is_error')) {
el.model.save('omemo_supported', true);
}
});
el.listenTo(el.model, 'change:omemo_supported', () => {
if (!el.model.get('omemo_supported') && el.model.get('omemo_active')) {
el.model.set('omemo_active', false);
} else {
// Manually trigger an update, setting omemo_active to
// false above will automatically trigger one.
el.querySelector('converse-chat-toolbar')?.requestUpdate();
}
});
el.listenTo(el.model, 'change:omemo_active', () => {
el.querySelector('converse-chat-toolbar').requestUpdate();
});
}
// TODO: add headless part of onChatInitialized

export function getSessionCipher (jid, id) {
const address = new libsignal.SignalProtocolAddress(jid, id);
return new window.libsignal.SessionCipher(_converse.omemo_store, address);
}


function getJIDForDecryption (attrs) {
const from_jid = attrs.from_muc ? attrs.from_real_jid : attrs.from;
if (!from_jid) {
Expand Down Expand Up @@ -459,6 +394,7 @@ export function addKeysToMessageStanza (stanza, dicts, iv) {
return Promise.resolve(stanza);
}


/**
* Given an XML element representing a user's OMEMO bundle, parse it
* and return a map.
Expand Down Expand Up @@ -676,6 +612,7 @@ export async function initOMEMO (reconnecting) {
api.trigger('OMEMOInitialized');
}


async function onOccupantAdded (chatroom, occupant) {
if (occupant.isSelf() || !chatroom.features.get('nonanonymous') || !chatroom.features.get('membersonly')) {
return;
Expand Down Expand Up @@ -710,71 +647,9 @@ async function checkOMEMOSupported (chatbox) {
}
}

function toggleOMEMO (ev) {
ev.stopPropagation();
ev.preventDefault();
const toolbar_el = u.ancestor(ev.target, 'converse-chat-toolbar');
if (!toolbar_el.model.get('omemo_supported')) {
let messages;
if (toolbar_el.model.get('type') === _converse.CHATROOMS_TYPE) {
messages = [
__(
'Cannot use end-to-end encryption in this groupchat, ' +
'either the groupchat has some anonymity or not all participants support OMEMO.'
)
];
} else {
messages = [
__(
"Cannot use end-to-end encryption because %1$s uses a client that doesn't support OMEMO.",
toolbar_el.model.contact.getDisplayName()
)
];
}
return api.alert('error', __('Error'), messages);
}
toolbar_el.model.save({ 'omemo_active': !toolbar_el.model.get('omemo_active') });
}

export function getOMEMOToolbarButton (toolbar_el, buttons) {
const model = toolbar_el.model;
const is_muc = model.get('type') === _converse.CHATROOMS_TYPE;
let title;
if (model.get('omemo_supported')) {
const i18n_plaintext = __('Messages are being sent in plaintext');
const i18n_encrypted = __('Messages are sent encrypted');
title = model.get('omemo_active') ? i18n_encrypted : i18n_plaintext;
} else if (is_muc) {
title = __(
'This groupchat needs to be members-only and non-anonymous in ' +
'order to support OMEMO encrypted messages'
);
} else {
title = __('OMEMO encryption is not supported');
}
// TODO: add headless version of toggleOMEMO function here
badrihippo marked this conversation as resolved.
Show resolved Hide resolved

let color;
if (model.get('omemo_supported')) {
if (model.get('omemo_active')) {
color = is_muc ? `var(--muc-color)` : `var(--chat-toolbar-btn-color)`;
} else {
color = `var(--error-color)`;
}
} else {
color = `var(--muc-toolbar-btn-disabled-color)`;
}
buttons.push(html`
<button class="toggle-omemo" title="${title}" data-disabled=${!model.get('omemo_supported')} @click=${toggleOMEMO}>
<converse-icon
class="fa ${model.get('omemo_active') ? `fa-lock` : `fa-unlock`}"
path-prefix="${api.settings.get('assets_path')}"
size="1em"
color="${color}"
></converse-icon>
</button>
`);
return buttons;
}
// TODO: add headless version of getOMEMOToolbarButton (if any)


async function getBundlesAndBuildSessions (chatbox) {
Expand Down Expand Up @@ -858,9 +733,3 @@ export async function createOMEMOMessageStanza (chat, data) {
stanza.c('encryption', { 'xmlns': Strophe.NS.EME, namespace: Strophe.NS.OMEMO });
return { message, stanza };
}

export const omemo = {
decryptMessage,
encryptMessage,
formatFingerprint
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
19 changes: 13 additions & 6 deletions src/plugins/omemo/index.js → src/plugins/omemo-views/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,26 @@ import omemo_api from './api.js';
import { _converse, api, converse } from '@converse/headless/core';
import {
createOMEMOMessageStanza,
encryptFile,
getOMEMOToolbarButton,
onChatInitialized,
} from './utils.js';

// Temporary: to be removed to headless if possible!
import {
// createOMEMOMessageStanza,
encryptFile,
// getOMEMOToolbarButton,
getOutgoingMessageAttributes,
handleEncryptedFiles,
handleMessageSendError,
initOMEMO,
omemo,
onChatBoxesInitialized,
onChatInitialized,
/onChatBoxesInitialized,
Fixed Show fixed Hide fixed
// onChatInitialized,
parseEncryptedMessage,
registerPEPPushHandler,
setEncryptedFileURL,
} from './utils.js';
} from '@converse/headless/plugins/omemo/utils.js'

const { Strophe } = converse.env;

Expand All @@ -40,13 +47,13 @@ Strophe.addNamespace('OMEMO_WHITELISTED', Strophe.NS.OMEMO + '.whitelisted');
Strophe.addNamespace('OMEMO_BUNDLES', Strophe.NS.OMEMO + '.bundles');


converse.plugins.add('converse-omemo', {
converse.plugins.add('converse-omemo-views', {
enabled (_converse) {
return (
window.libsignal &&
_converse.config.get('trusted') &&
!api.settings.get('clear_cache_on_logout') &&
!_converse.api.settings.get('blacklisted_plugins').includes('converse-omemo')
!_converse.api.settings.get('blacklisted_plugins').includes('converse-omemo-views')
);
},

Expand Down
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { __ } from 'i18n';
import { html } from 'lit';
import { formatFingerprint } from '../utils.js';
import { formatFingerprint } from '@converse/headless/plugins/omemo/utils.js';

const device_fingerprint = (el, device) => {
const i18n_trusted = __('Trusted');
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import spinner from "templates/spinner.js";
import { formatFingerprint } from 'plugins/omemo/utils.js';
import { formatFingerprint } from '@converse/headless/plugins/omemo/utils.js';
import { html } from "lit";
import { __ } from 'i18n';

Expand Down
File renamed without changes.
Loading