Restrict editing of MUC messages...

to ones with the same XEP-0421 occupant ID
This commit is contained in:
JC Brand 2022-08-11 22:43:04 +02:00
parent 342c75775b
commit 7028286855
9 changed files with 114 additions and 22 deletions

View File

@ -10,6 +10,7 @@
- Move the `converse-oauth` plugin to the [community-plugins](https://github.com/conversejs/community-plugins)
- Don't apply message corrections when the MUC occupant-id doesn't match.
- Update `nick` attribute on ChatRoom when user nickname changes
- Restrict editing of MUC messages to ones with the same XEP-0421 occupant ID
- #2936: Fix documentation about enable_smacks option, which is true by default.
## 9.1.1 (2022-05-05)

View File

@ -234,7 +234,7 @@ const ChatBox = ModelWithContact.extend({
this.notifications.set('chat_state', attrs.chat_state);
}
if (u.shouldCreateMessage(attrs)) {
const msg = handleCorrection(this, attrs) || await this.createMessage(attrs);
const msg = await handleCorrection(this, attrs) || await this.createMessage(attrs);
this.notifications.set({'chat_state': null});
this.handleUnreadMessage(msg);
}

View File

@ -2280,7 +2280,7 @@ const ChatRoomMixin = {
this.updateNotifications(attrs.nick, attrs.chat_state);
}
if (u.shouldCreateGroupchatMessage(attrs)) {
const msg = handleCorrection(this, attrs) || (await this.createMessage(attrs));
const msg = await handleCorrection(this, attrs) || (await this.createMessage(attrs));
this.removeNotification(attrs.nick, ['composing', 'paused']);
this.handleUnreadMessage(msg);
}

View File

@ -245,12 +245,15 @@ export async function parseMUCMessage (stanza, chatbox) {
const from_real_jid = attrs.is_archived && getJIDFromMUCUserData(stanza, attrs) ||
chatbox.occupants.findOccupant(attrs)?.get('jid');
const own_occupant_id = chatbox.get('occupant_id');
const is_me = attrs.occupant_id && own_occupant_id ? own_occupant_id === attrs.occupant_id : attrs.nick === chatbox.get('nick');
attrs = Object.assign( {
from_real_jid,
'is_only_emojis': attrs.body ? u.isOnlyEmojis(attrs.body) : false,
'is_valid_receipt_request': isValidReceiptRequest(stanza, attrs),
'message': attrs.body || attrs.error, // TODO: Remove and use body and error attributes instead
'sender': attrs.nick === chatbox.get('nick') ? 'me' : 'them',
'message': attrs.body || attrs.error, // TODO: Should only be used for error and info messages
'sender': is_me ? 'me' : 'them',
}, attrs);
if (attrs.is_archived && original_stanza.getAttribute('from') !== attrs.from_muc) {

View File

@ -72,7 +72,7 @@ export function getMediaURLs (arr, text, offset=0) {
* @returns { _converse.Message|undefined } Returns the corrected
* message or `undefined` if not applicable.
*/
export function handleCorrection (model, attrs) {
export async function handleCorrection (model, attrs) {
if (!attrs.replace_id || !attrs.from) {
return;
}
@ -84,7 +84,8 @@ export function handleCorrection (model, attrs) {
const message = model.messages.models.find(query);
if (!message) {
return;
attrs['older_versions'] = [];
return await model.createMessage(attrs); // eslint-disable-line no-return-await
}
const older_versions = message.get('older_versions') || {};

View File

@ -296,10 +296,11 @@ describe('A Groupchat Message XEP-0308 correction ', function () {
from="lounge@montague.lit/newguy"
to="_converse.connection.jid"
type="groupchat"
id="${msg_id}">
id="${u.getUniqueId()}">
<body>But soft, what light through yonder chimney breaks?</body>
<occupant-id xmlns="urn:xmpp:occupant-id:0" id="2"></occupant-id>
<replace id="${msg_id}" xmlns="urn:xmpp:message-correct:0"></replace>
</message>`
);
@ -309,7 +310,7 @@ describe('A Groupchat Message XEP-0308 correction ', function () {
expect(model.messages.at(0).get('edited')).toBeFalsy();
expect(model.messages.at(1).get('body')).toBe('But soft, what light through yonder chimney breaks?');
expect(model.messages.at(1).get('edited')).toBeFalsy();
expect(model.messages.at(1).get('edited')).toBeTruthy();
await model.handleMessageStanza(
stx`
@ -317,9 +318,10 @@ describe('A Groupchat Message XEP-0308 correction ', function () {
from="lounge@montague.lit/newguy"
to="_converse.connection.jid"
type="groupchat"
id="${msg_id}">
id="${u.getUniqueId()}">
<body>But soft, what light through yonder hatch breaks?</body>
<replace id="${msg_id}" xmlns="urn:xmpp:message-correct:0"></replace>
</message>`
);
@ -329,13 +331,91 @@ describe('A Groupchat Message XEP-0308 correction ', function () {
expect(model.messages.at(0).get('edited')).toBeFalsy();
expect(model.messages.at(1).get('body')).toBe('But soft, what light through yonder chimney breaks?');
expect(model.messages.at(1).get('edited')).toBeFalsy();
expect(model.messages.at(1).get('edited')).toBeTruthy();
expect(model.messages.at(2).get('body')).toBe('But soft, what light through yonder hatch breaks?');
expect(model.messages.at(2).get('edited')).toBeFalsy();
expect(model.messages.at(2).get('edited')).toBeTruthy();
const message_els = Array.from(view.querySelectorAll('.chat-msg'));
expect(message_els.reduce((acc, m) => acc && u.hasClass('chat-msg--followup', m), true)).toBe(false);
})
);
it(
"cannot be edited if it's from a different occupant id",
mock.initConverse([], {}, async function (_converse) {
const nick = 'romeo';
const muc_jid = 'lounge@montague.lit';
const features = [...mock.default_muc_features, Strophe.NS.OCCUPANTID];
const model = await mock.openAndEnterChatRoom(_converse, muc_jid, nick, features);
expect(model.get('occupant_id')).toBe(model.occupants.at(0).get('occupant_id'));
const msg_id = u.getUniqueId();
await model.handleMessageStanza(
stx`
<message
from="lounge@montague.lit/${nick}"
to="_converse.connection.jid"
type="groupchat"
id="${msg_id}">
<body>But soft, what light through yonder airlock breaks?</body>
<occupant-id xmlns="urn:xmpp:occupant-id:0" id="${model.get('occupant_id')}"></occupant-id>
</message>`
);
const view = _converse.chatboxviews.get(muc_jid);
await u.waitUntil(() => view.querySelectorAll('.chat-msg').length);
expect(model.messages.at(0).get('body')).toBe('But soft, what light through yonder airlock breaks?');
await model.handleMessageStanza(
stx`
<message
from="lounge@montague.lit/${nick}"
to="_converse.connection.jid"
type="groupchat"
id="${u.getUniqueId()}">
<body>But soft, what light through yonder chimney breaks?</body>
<occupant-id xmlns="urn:xmpp:occupant-id:0" id="${model.get('occupant_id')}"></occupant-id>
<replace id="${msg_id}" xmlns="urn:xmpp:message-correct:0"></replace>
</message>`
);
expect(model.messages.at(0).get('body')).toBe('But soft, what light through yonder chimney breaks?');
expect(model.messages.at(0).get('edited')).toBeTruthy();
await model.handleMessageStanza(
stx`
<message
from="lounge@montague.lit/${nick}"
to="_converse.connection.jid"
type="groupchat"
id="${u.getUniqueId()}">
<body>But soft, what light through yonder hatch breaks?</body>
<occupant-id xmlns="urn:xmpp:occupant-id:0" id="${u.getUniqueId()}"></occupant-id>
<replace id="${msg_id}" xmlns="urn:xmpp:message-correct:0"></replace>
</message>`
);
await u.waitUntil(() => view.querySelectorAll('.chat-msg').length === 2);
expect(model.messages.length).toBe(2);
expect(model.messages.at(0).get('body')).toBe('But soft, what light through yonder chimney breaks?');
expect(model.messages.at(0).get('edited')).toBeTruthy();
expect(model.messages.at(0).get('editable')).toBeTruthy();
expect(model.messages.at(1).get('body')).toBe('But soft, what light through yonder hatch breaks?');
expect(model.messages.at(1).get('edited')).toBeTruthy();
expect(model.messages.at(1).get('editable')).toBeFalsy();
const message_els = Array.from(view.querySelectorAll('.chat-msg'));
expect(message_els.reduce((acc, m) => acc && u.hasClass('chat-msg--followup', m), true)).toBe(false);
// We can edit our own message, but not the other
expect(message_els[0].querySelector('converse-dropdown .chat-msg__action-edit')).toBeDefined();
expect(message_els[1].querySelector('converse-dropdown .chat-msg__action-edit')).toBe(null);
})
);
});

View File

@ -1,9 +1,13 @@
import { CustomElement } from './element.js';
import { api, converse } from '@converse/headless/core';
import { html } from 'lit';
import { __ } from 'i18n';
import './styles/message-versions.scss';
const { dayjs } = converse.env;
const tpl_older_version = (k, older_versions) => html`<p class="older-msg"><time>${dayjs(k).format('MMM D, YYYY, HH:mm:ss')}</time>: ${older_versions[k]}</p>`;
export class MessageVersions extends CustomElement {
@ -15,13 +19,15 @@ export class MessageVersions extends CustomElement {
render () {
const older_versions = this.model.get('older_versions');
const keys = Object.keys(older_versions);
return html`
<h4>Older versions</h4>
${ Object.keys(older_versions).map(
k => html`<p class="older-msg"><time>${dayjs(k).format('MMM D, YYYY, HH:mm:ss')}</time>: ${older_versions[k]}</p>`) }
${ keys.length ?
html`<h4>${__('Older versions')}</h4> ${keys.map(k => tpl_older_version(k, older_versions))}` :
html`<h4>${__('No older versions found')}</h4>`
}
<hr/>
<h4>Current version</h4>
<p>${this.model.getMessageText()}</p>`;
<h4>${__('Current version')}</h4>
<p><time>${dayjs(this.model.get('time')).format('MMM D, YYYY, HH:mm:ss')}</time>: ${this.model.getMessageText()}</p>`;
}
}

View File

@ -0,0 +1,7 @@
.conversejs {
converse-message-versions {
time {
font-weight: bold;
}
}
}

View File

@ -3,12 +3,6 @@
color: var(--subdued-color);
}
.older-msg {
time {
font-weight: bold;
}
}
.message {
.show-msg-author-modal {
align-self: flex-start; // Don't expand height to that of largest sibling