implement XEP-0184: Message Delivery Receipts
This commit is contained in:
parent
e3a5bf7e23
commit
da5ca0b585
@ -5,6 +5,7 @@
|
||||
- Error `FATAL: TypeError: Cannot read property 'extend' of undefined` when using `embedded` view mode.
|
||||
- Default paths in converse-notifications.js are now relative
|
||||
- Add a button to regenerate OMEMO keys
|
||||
- #141 XEP-0184: Message Delivery Receipts
|
||||
- #1188 Feature request: drag and drop file to HTTP Upload
|
||||
- #1268 Switch from SASS variables to CSS custom properties
|
||||
- #1278 Replace the default avatar with a SVG version
|
||||
|
@ -9303,6 +9303,7 @@ readers do not read off random characters that represent icons */
|
||||
--text-color: #666;
|
||||
--text-color-lighten-15-percent: #8c8c8c;
|
||||
--message-text-color: #555;
|
||||
--message-receipt-color: #3AA569;
|
||||
--save-button-color: #3AA569;
|
||||
--chat-textarea-color: #666;
|
||||
--chat-textarea-height: 60px;
|
||||
@ -11796,6 +11797,8 @@ body.reset {
|
||||
display: none; }
|
||||
#conversejs .message.chat-msg.chat-msg--followup .chat-msg__content {
|
||||
margin-left: 2.75rem; }
|
||||
#conversejs .message.chat-msg .chat-msg__receipt {
|
||||
color: var(--message-receipt-color); }
|
||||
|
||||
#conversejs .chatroom-body .message.onload {
|
||||
animation: colorchange-chatmessage-muc 1s;
|
||||
|
75
dist/converse.js
vendored
75
dist/converse.js
vendored
@ -61682,7 +61682,7 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].plugins
|
||||
return this.renderFileUploadProgresBar();
|
||||
}
|
||||
|
||||
if (_.filter(['correcting', 'message', 'type', 'upload'], prop => Object.prototype.hasOwnProperty.call(this.model.changed, prop)).length) {
|
||||
if (_.filter(['correcting', 'message', 'type', 'upload', 'received'], prop => Object.prototype.hasOwnProperty.call(this.model.changed, prop)).length) {
|
||||
await this.render();
|
||||
}
|
||||
|
||||
@ -65704,7 +65704,9 @@ _converse_headless_converse_core__WEBPACK_IMPORTED_MODULE_0__["default"].plugins
|
||||
'to': this.get('jid'),
|
||||
'type': this.get('message_type'),
|
||||
'id': message.get('msgid')
|
||||
}).c('body').t(body).up() // An encrypted header is added to the message for
|
||||
}).c('body').t(body).up().c('request', {
|
||||
'xmlns': Strophe.NS.RECEIPTS
|
||||
}).up() // An encrypted header is added to the message for
|
||||
// each device that is supposed to receive it.
|
||||
// These headers simply contain the key that the
|
||||
// payload message is encrypted with,
|
||||
@ -70630,6 +70632,7 @@ const _converse$env = _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].env
|
||||
_ = _converse$env._;
|
||||
const u = _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].env.utils;
|
||||
Strophe.addNamespace('MESSAGE_CORRECT', 'urn:xmpp:message-correct:0');
|
||||
Strophe.addNamespace('RECEIPTS', 'urn:xmpp:receipts');
|
||||
Strophe.addNamespace('REFERENCE', 'urn:xmpp:reference:0');
|
||||
_converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-chatboxes', {
|
||||
dependencies: ["converse-roster", "converse-vcard"],
|
||||
@ -70940,6 +70943,31 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-cha
|
||||
return false;
|
||||
},
|
||||
|
||||
handleReceipt(stanza) {
|
||||
const to_bare_jid = Strophe.getBareJidFromJid(stanza.getAttribute('to'));
|
||||
|
||||
if (to_bare_jid === _converse.bare_jid) {
|
||||
const receipt = sizzle(`received[xmlns="${Strophe.NS.RECEIPTS}"]`, stanza).pop();
|
||||
|
||||
if (receipt) {
|
||||
const msgid = receipt && receipt.getAttribute('id'),
|
||||
message = msgid && this.messages.findWhere({
|
||||
msgid
|
||||
});
|
||||
|
||||
if (message && !message.get('received')) {
|
||||
message.save({
|
||||
'received': moment().format()
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
createMessageStanza(message) {
|
||||
/* Given a _converse.Message Backbone.Model, return the XML
|
||||
* stanza that represents it.
|
||||
@ -70954,6 +70982,8 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-cha
|
||||
'id': message.get('edited') && _converse.connection.getUniqueId() || message.get('msgid')
|
||||
}).c('body').t(message.get('message')).up().c(_converse.ACTIVE, {
|
||||
'xmlns': Strophe.NS.CHATSTATES
|
||||
}).up().c('request', {
|
||||
'xmlns': Strophe.NS.RECEIPTS
|
||||
}).up();
|
||||
|
||||
if (message.get('is_spoiler')) {
|
||||
@ -71344,6 +71374,19 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-cha
|
||||
}
|
||||
},
|
||||
|
||||
sendReceiptStanza(to_jid, id) {
|
||||
const receipt_stanza = $msg({
|
||||
'from': _converse.connection.jid,
|
||||
'id': _converse.connection.getUniqueId(),
|
||||
'to': to_jid
|
||||
}).c('received', {
|
||||
'xmlns': Strophe.NS.RECEIPTS,
|
||||
'id': id
|
||||
}).up();
|
||||
|
||||
_converse.api.send(receipt_stanza);
|
||||
},
|
||||
|
||||
onMessage(stanza) {
|
||||
/* Handler method for all incoming single-user chat "message"
|
||||
* stanzas.
|
||||
@ -71387,6 +71430,12 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-cha
|
||||
to_jid = stanza.getAttribute('to');
|
||||
}
|
||||
|
||||
const requests_receipt = !_.isUndefined(sizzle(`request[xmlns="${Strophe.NS.RECEIPTS}"]`, stanza).pop());
|
||||
|
||||
if (requests_receipt) {
|
||||
this.sendReceiptStanza(from_jid, stanza.getAttribute('id'));
|
||||
}
|
||||
|
||||
const from_bare_jid = Strophe.getBareJidFromJid(from_jid),
|
||||
from_resource = Strophe.getResourceFromJid(from_jid),
|
||||
is_me = from_bare_jid === _converse.bare_jid;
|
||||
@ -71410,7 +71459,7 @@ _converse_core__WEBPACK_IMPORTED_MODULE_2__["default"].plugins.add('converse-cha
|
||||
const has_body = sizzle(`body, encrypted[xmlns="${Strophe.NS.OMEMO}"]`).length > 0;
|
||||
const chatbox = this.getChatBox(contact_jid, attrs, has_body);
|
||||
|
||||
if (chatbox && !chatbox.handleMessageCorrection(stanza)) {
|
||||
if (chatbox && !chatbox.handleMessageCorrection(stanza) && !chatbox.handleReceipt(stanza)) {
|
||||
const msgid = stanza.getAttribute('id'),
|
||||
message = msgid && chatbox.messages.findWhere({
|
||||
msgid
|
||||
@ -76065,15 +76114,23 @@ _converse_core__WEBPACK_IMPORTED_MODULE_6__["default"].plugins.add('converse-muc
|
||||
return data;
|
||||
},
|
||||
|
||||
isDuplicate(message, original_stanza) {
|
||||
isDuplicate(message) {
|
||||
const msgid = message.getAttribute('id'),
|
||||
jid = message.getAttribute('from');
|
||||
|
||||
if (msgid) {
|
||||
return this.messages.where({
|
||||
const msg = this.messages.findWhere({
|
||||
'msgid': msgid,
|
||||
'from': jid
|
||||
}).length;
|
||||
});
|
||||
|
||||
if (msg && msg.get('sender') === 'me' && !msg.get('received')) {
|
||||
msg.save({
|
||||
'received': moment().format()
|
||||
});
|
||||
}
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -76106,7 +76163,7 @@ _converse_core__WEBPACK_IMPORTED_MODULE_6__["default"].plugins.add('converse-muc
|
||||
stanza = forwarded.querySelector('message');
|
||||
}
|
||||
|
||||
if (this.isDuplicate(stanza, original_stanza)) {
|
||||
if (this.isDuplicate(stanza)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -102447,6 +102504,10 @@ __p += '\n </span>\n ';
|
||||
if (!o.is_me_message) { ;
|
||||
__p += '<div class="chat-msg__body">';
|
||||
} ;
|
||||
__p += '\n ';
|
||||
if (o.received) { ;
|
||||
__p += ' <span class="fa fa-check chat-msg__receipt"> </span> ';
|
||||
} ;
|
||||
__p += '\n ';
|
||||
if (o.edited) { ;
|
||||
__p += ' <i title="' +
|
||||
|
@ -256,6 +256,10 @@
|
||||
margin-left: 2.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-msg__receipt {
|
||||
color: var(--message-receipt-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -28,6 +28,7 @@ $font-path: "webfonts/icomoon/fonts/" !default;
|
||||
--text-color: #666;
|
||||
--text-color-lighten-15-percent: #8c8c8c; // lighten(#666, 15%)
|
||||
--message-text-color: #555;
|
||||
--message-receipt-color: #3AA569; // $green
|
||||
--save-button-color: #3AA569; // $green
|
||||
|
||||
--chat-textarea-color: #666;
|
||||
|
@ -357,6 +357,7 @@
|
||||
`xmlns="jabber:client">`+
|
||||
`<body>${message}</body>`+
|
||||
`<active xmlns="http://jabber.org/protocol/chatstates"/>`+
|
||||
`<request xmlns="urn:xmpp:receipts"/>`+
|
||||
`<x xmlns="jabber:x:oob">`+
|
||||
`<url>${message}</url>`+
|
||||
`</x>`+
|
||||
@ -459,6 +460,7 @@
|
||||
`xmlns="jabber:client">`+
|
||||
`<body>${message}</body>`+
|
||||
`<active xmlns="http://jabber.org/protocol/chatstates"/>`+
|
||||
`<request xmlns="urn:xmpp:receipts"/>`+
|
||||
`<x xmlns="jabber:x:oob">`+
|
||||
`<url>${message}</url>`+
|
||||
`</x>`+
|
||||
|
@ -77,6 +77,7 @@
|
||||
`xmlns="jabber:client">`+
|
||||
`<body>But soft, what light through yonder window breaks?</body>`+
|
||||
`<active xmlns="http://jabber.org/protocol/chatstates"/>`+
|
||||
`<request xmlns="urn:xmpp:receipts"/>`+
|
||||
`<replace id="${first_msg.get("msgid")}" xmlns="urn:xmpp:message-correct:0"/>`+
|
||||
`</message>`);
|
||||
expect(view.model.messages.models.length).toBe(1);
|
||||
@ -181,6 +182,7 @@
|
||||
`xmlns="jabber:client">`+
|
||||
`<body>But soft, what light through yonder window breaks?</body>`+
|
||||
`<active xmlns="http://jabber.org/protocol/chatstates"/>`+
|
||||
`<request xmlns="urn:xmpp:receipts"/>`+
|
||||
`<replace id="${first_msg.get("msgid")}" xmlns="urn:xmpp:message-correct:0"/>`+
|
||||
`</message>`);
|
||||
expect(view.model.messages.models.length).toBe(1);
|
||||
@ -1200,6 +1202,64 @@
|
||||
done();
|
||||
}));
|
||||
|
||||
it("received may emit a message delivery receipt",
|
||||
mock.initConverseWithPromises(
|
||||
null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
|
||||
function (done, _converse) {
|
||||
test_utils.createContacts(_converse, 'current', 1);
|
||||
const sender_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
|
||||
const msg_id = u.getUniqueId();
|
||||
const sent_stanzas = [];
|
||||
spyOn(_converse.connection, 'send').and.callFake(function (stanza) {
|
||||
sent_stanzas.push(stanza);
|
||||
});
|
||||
const msg = $msg({
|
||||
'from': sender_jid,
|
||||
'to': _converse.connection.jid,
|
||||
'type': 'chat',
|
||||
'id': msg_id,
|
||||
}).c('body').t('Message!').up()
|
||||
.c('request', {'xmlns': Strophe.NS.RECEIPTS}).tree();
|
||||
_converse.chatboxes.onMessage(msg);
|
||||
const receipt = sizzle(`received[xmlns="${Strophe.NS.RECEIPTS}"]`, sent_stanzas[0].tree()).pop();
|
||||
expect(receipt.outerHTML).toBe(`<received xmlns="${Strophe.NS.RECEIPTS}" id="${msg_id}"/>`);
|
||||
done();
|
||||
}));
|
||||
|
||||
it("delivery can be acknowledged by a receipt",
|
||||
mock.initConverseWithPromises(
|
||||
null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
|
||||
async function (done, _converse) {
|
||||
|
||||
test_utils.createContacts(_converse, 'current', 1);
|
||||
_converse.emit('rosterContactsFetched');
|
||||
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
|
||||
await test_utils.openChatBoxFor(_converse, contact_jid);
|
||||
const view = _converse.chatboxviews.get(contact_jid);
|
||||
const textarea = view.el.querySelector('textarea.chat-textarea');
|
||||
textarea.value = 'But soft, what light through yonder airlock breaks?';
|
||||
view.keyPressed({
|
||||
target: textarea,
|
||||
preventDefault: _.noop,
|
||||
keyCode: 13 // Enter
|
||||
});
|
||||
await test_utils.waitUntil(() => _converse.api.chats.get().length);
|
||||
const chatbox = _converse.chatboxes.get(contact_jid);
|
||||
expect(chatbox).toBeDefined();
|
||||
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
|
||||
const msg_obj = chatbox.messages.models[0];
|
||||
const msg_id = msg_obj.get('msgid');
|
||||
const msg = $msg({
|
||||
'from': contact_jid,
|
||||
'to': _converse.connection.jid,
|
||||
'id': u.getUniqueId(),
|
||||
}).c('received', {'id': msg_id, xmlns: Strophe.NS.RECEIPTS}).up().tree();
|
||||
_converse.chatboxes.onMessage(msg);
|
||||
await new Promise((resolve, reject) => view.model.messages.once('rendered', resolve));
|
||||
expect(view.el.querySelectorAll('.chat-msg__receipt').length).toBe(1);
|
||||
done();
|
||||
}));
|
||||
|
||||
|
||||
describe("when received from someone else", function () {
|
||||
|
||||
@ -2010,6 +2070,7 @@
|
||||
`xmlns="jabber:client">`+
|
||||
`<body>But soft, what light through yonder window breaks?</body>`+
|
||||
`<active xmlns="http://jabber.org/protocol/chatstates"/>`+
|
||||
`<request xmlns="urn:xmpp:receipts"/>`+
|
||||
`<replace id="${first_msg.get("msgid")}" xmlns="urn:xmpp:message-correct:0"/>`+
|
||||
`</message>`);
|
||||
|
||||
@ -2056,6 +2117,38 @@
|
||||
done();
|
||||
}));
|
||||
|
||||
it("delivery can be acknowledged by a receipt",
|
||||
mock.initConverseWithPromises(
|
||||
null, ['rosterGroupsFetched'], {},
|
||||
async function (done, _converse) {
|
||||
|
||||
test_utils.createContacts(_converse, 'current');
|
||||
await test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy');
|
||||
const view = _converse.chatboxviews.get('lounge@localhost');
|
||||
const textarea = view.el.querySelector('textarea.chat-textarea');
|
||||
textarea.value = 'But soft, what light through yonder airlock breaks?';
|
||||
view.keyPressed({
|
||||
target: textarea,
|
||||
preventDefault: _.noop,
|
||||
keyCode: 13 // Enter
|
||||
});
|
||||
await new Promise((resolve, reject) => view.once('messageInserted', resolve));
|
||||
const msg_obj = view.model.messages.at(0);
|
||||
const msg_id = msg_obj.get('msgid');
|
||||
const from = msg_obj.get('from');
|
||||
const body = msg_obj.get('message');
|
||||
const msg = $msg({
|
||||
'from': from,
|
||||
'id': msg_id,
|
||||
'to': 'dummy@localhost',
|
||||
'type': 'groupchat',
|
||||
}).c('body').t(body).up().tree();
|
||||
view.model.onMessage(msg);
|
||||
await new Promise((resolve, reject) => view.model.messages.once('rendered', resolve));
|
||||
expect(view.el.querySelectorAll('.chat-msg__receipt').length).toBe(1);
|
||||
done();
|
||||
}));
|
||||
|
||||
describe("when received", function () {
|
||||
|
||||
it("highlights all users mentioned via XEP-0372 references",
|
||||
@ -2201,6 +2294,7 @@
|
||||
`xmlns="jabber:client">`+
|
||||
`<body>hello z3r0 gibson mr.robot, how are you?</body>`+
|
||||
`<active xmlns="http://jabber.org/protocol/chatstates"/>`+
|
||||
`<request xmlns="urn:xmpp:receipts"/>`+
|
||||
`<reference begin="18" end="26" type="mention" uri="xmpp:mr.robot@localhost" xmlns="urn:xmpp:reference:0"/>`+
|
||||
`<reference begin="11" end="17" type="mention" uri="xmpp:gibson@localhost" xmlns="urn:xmpp:reference:0"/>`+
|
||||
`<reference begin="6" end="10" type="mention" uri="xmpp:z3r0@localhost" xmlns="urn:xmpp:reference:0"/>`+
|
||||
@ -2226,6 +2320,7 @@
|
||||
`xmlns="jabber:client">`+
|
||||
`<body>hello z3r0 gibson sw0rdf1sh, how are you?</body>`+
|
||||
`<active xmlns="http://jabber.org/protocol/chatstates"/>`+
|
||||
`<request xmlns="urn:xmpp:receipts"/>`+
|
||||
`<reference begin="18" end="27" type="mention" uri="xmpp:sw0rdf1sh@localhost" xmlns="urn:xmpp:reference:0"/>`+
|
||||
`<reference begin="11" end="17" type="mention" uri="xmpp:gibson@localhost" xmlns="urn:xmpp:reference:0"/>`+
|
||||
`<reference begin="6" end="10" type="mention" uri="xmpp:z3r0@localhost" xmlns="urn:xmpp:reference:0"/>`+
|
||||
@ -2274,6 +2369,7 @@
|
||||
`xmlns="jabber:client">`+
|
||||
`<body>hello z3r0 gibson mr.robot, how are you?</body>`+
|
||||
`<active xmlns="http://jabber.org/protocol/chatstates"/>`+
|
||||
`<request xmlns="urn:xmpp:receipts"/>`+
|
||||
`<reference begin="18" end="26" type="mention" uri="xmpp:mr.robot@localhost" xmlns="urn:xmpp:reference:0"/>`+
|
||||
`<reference begin="11" end="17" type="mention" uri="xmpp:gibson@localhost" xmlns="urn:xmpp:reference:0"/>`+
|
||||
`<reference begin="6" end="10" type="mention" uri="xmpp:z3r0@localhost" xmlns="urn:xmpp:reference:0"/>`+
|
||||
|
@ -172,6 +172,7 @@
|
||||
`to="max.frankfurter@localhost" `+
|
||||
`type="chat" xmlns="jabber:client">`+
|
||||
`<body>This is an OMEMO encrypted message which your client doesn’t seem to support. Find more information on https://conversations.im/omemo</body>`+
|
||||
`<request xmlns="urn:xmpp:receipts"/>`+
|
||||
`<encrypted xmlns="eu.siacs.conversations.axolotl">`+
|
||||
`<header sid="123456789">`+
|
||||
`<key rid="482886413b977930064a5888b92134fe">YzFwaDNSNzNYNw==</key>`+
|
||||
|
@ -86,7 +86,7 @@ converse.plugins.add('converse-message-view', {
|
||||
if (this.model.changed.progress) {
|
||||
return this.renderFileUploadProgresBar();
|
||||
}
|
||||
if (_.filter(['correcting', 'message', 'type', 'upload'],
|
||||
if (_.filter(['correcting', 'message', 'type', 'upload', 'received'],
|
||||
prop => Object.prototype.hasOwnProperty.call(this.model.changed, prop)).length) {
|
||||
await this.render();
|
||||
}
|
||||
|
@ -394,6 +394,7 @@ converse.plugins.add('converse-omemo', {
|
||||
'type': this.get('message_type'),
|
||||
'id': message.get('msgid')
|
||||
}).c('body').t(body).up()
|
||||
.c('request', {'xmlns': Strophe.NS.RECEIPTS}).up()
|
||||
// An encrypted header is added to the message for
|
||||
// each device that is supposed to receive it.
|
||||
// These headers simply contain the key that the
|
||||
|
@ -13,6 +13,7 @@ const { $msg, Backbone, Promise, Strophe, b64_sha1, moment, sizzle, utils, _ } =
|
||||
const u = converse.env.utils;
|
||||
|
||||
Strophe.addNamespace('MESSAGE_CORRECT', 'urn:xmpp:message-correct:0');
|
||||
Strophe.addNamespace('RECEIPTS', 'urn:xmpp:receipts');
|
||||
Strophe.addNamespace('REFERENCE', 'urn:xmpp:reference:0');
|
||||
|
||||
|
||||
@ -297,6 +298,24 @@ converse.plugins.add('converse-chatboxes', {
|
||||
return false;
|
||||
},
|
||||
|
||||
handleReceipt (stanza) {
|
||||
const to_bare_jid = Strophe.getBareJidFromJid(stanza.getAttribute('to'));
|
||||
if (to_bare_jid === _converse.bare_jid) {
|
||||
const receipt = sizzle(`received[xmlns="${Strophe.NS.RECEIPTS}"]`, stanza).pop();
|
||||
if (receipt) {
|
||||
const msgid = receipt && receipt.getAttribute('id'),
|
||||
message = msgid && this.messages.findWhere({msgid});
|
||||
if (message && !message.get('received')) {
|
||||
message.save({
|
||||
'received': moment().format()
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
createMessageStanza (message) {
|
||||
/* Given a _converse.Message Backbone.Model, return the XML
|
||||
* stanza that represents it.
|
||||
@ -310,7 +329,8 @@ converse.plugins.add('converse-chatboxes', {
|
||||
'type': this.get('message_type'),
|
||||
'id': message.get('edited') && _converse.connection.getUniqueId() || message.get('msgid'),
|
||||
}).c('body').t(message.get('message')).up()
|
||||
.c(_converse.ACTIVE, {'xmlns': Strophe.NS.CHATSTATES}).up();
|
||||
.c(_converse.ACTIVE, {'xmlns': Strophe.NS.CHATSTATES}).up()
|
||||
.c('request', {'xmlns': Strophe.NS.RECEIPTS}).up();
|
||||
|
||||
if (message.get('is_spoiler')) {
|
||||
if (message.get('spoiler_hint')) {
|
||||
@ -663,6 +683,15 @@ converse.plugins.add('converse-chatboxes', {
|
||||
}
|
||||
},
|
||||
|
||||
sendReceiptStanza (to_jid, id) {
|
||||
const receipt_stanza = $msg({
|
||||
'from': _converse.connection.jid,
|
||||
'id': _converse.connection.getUniqueId(),
|
||||
'to': to_jid,
|
||||
}).c('received', {'xmlns': Strophe.NS.RECEIPTS, 'id': id}).up();
|
||||
_converse.api.send(receipt_stanza);
|
||||
},
|
||||
|
||||
onMessage (stanza) {
|
||||
/* Handler method for all incoming single-user chat "message"
|
||||
* stanzas.
|
||||
@ -709,6 +738,11 @@ converse.plugins.add('converse-chatboxes', {
|
||||
to_jid = stanza.getAttribute('to');
|
||||
}
|
||||
|
||||
const requests_receipt = !_.isUndefined(sizzle(`request[xmlns="${Strophe.NS.RECEIPTS}"]`, stanza).pop());
|
||||
if (requests_receipt) {
|
||||
this.sendReceiptStanza(from_jid, stanza.getAttribute('id'));
|
||||
}
|
||||
|
||||
const from_bare_jid = Strophe.getBareJidFromJid(from_jid),
|
||||
from_resource = Strophe.getResourceFromJid(from_jid),
|
||||
is_me = from_bare_jid === _converse.bare_jid;
|
||||
@ -732,7 +766,7 @@ converse.plugins.add('converse-chatboxes', {
|
||||
// Get chat box, but only create a new one when the message has a body.
|
||||
const has_body = sizzle(`body, encrypted[xmlns="${Strophe.NS.OMEMO}"]`).length > 0;
|
||||
const chatbox = this.getChatBox(contact_jid, attrs, has_body);
|
||||
if (chatbox && !chatbox.handleMessageCorrection(stanza)) {
|
||||
if (chatbox && !chatbox.handleMessageCorrection(stanza) && !chatbox.handleReceipt(stanza)) {
|
||||
const msgid = stanza.getAttribute('id'),
|
||||
message = msgid && chatbox.messages.findWhere({msgid});
|
||||
if (!message) {
|
||||
|
@ -913,13 +913,21 @@ converse.plugins.add('converse-muc', {
|
||||
return data;
|
||||
},
|
||||
|
||||
isDuplicate (message, original_stanza) {
|
||||
isDuplicate (message) {
|
||||
const msgid = message.getAttribute('id'),
|
||||
jid = message.getAttribute('from');
|
||||
|
||||
if (msgid) {
|
||||
return this.messages.where({'msgid': msgid, 'from': jid}).length;
|
||||
const msg = this.messages.findWhere({'msgid': msgid, 'from': jid});
|
||||
if (msg && msg.get('sender') === 'me' && !msg.get('received')) {
|
||||
msg.save({
|
||||
'received': moment().format()
|
||||
});
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
return false;
|
||||
|
||||
},
|
||||
|
||||
fetchFeaturesIfConfigurationChanged (stanza) {
|
||||
@ -949,7 +957,7 @@ converse.plugins.add('converse-muc', {
|
||||
if (!_.isNull(forwarded)) {
|
||||
stanza = forwarded.querySelector('message');
|
||||
}
|
||||
if (this.isDuplicate(stanza, original_stanza)) {
|
||||
if (this.isDuplicate(stanza)) {
|
||||
return;
|
||||
}
|
||||
const jid = stanza.getAttribute('from'),
|
||||
|
@ -12,6 +12,7 @@
|
||||
{[ if (o.is_encrypted) { ]}<span class="fa fa-lock"></span>{[ } ]}
|
||||
</span>
|
||||
{[ if (!o.is_me_message) { ]}<div class="chat-msg__body">{[ } ]}
|
||||
{[ if (o.received) { ]} <span class="fa fa-check chat-msg__receipt"> </span> {[ } ]}
|
||||
{[ if (o.edited) { ]} <i title="{{{o.__('This message has been edited')}}}" class="fa fa-edit chat-msg__edit-modal"></i> {[ } ]}
|
||||
{[ if (!o.is_me_message) { ]}<div class="chat-msg__message">{[ } ]}
|
||||
{[ if (o.is_spoiler) { ]}
|
||||
|
Loading…
Reference in New Issue
Block a user