Commit e7f211ba authored by JC Brand's avatar JC Brand

Avoid an unnecessary promise and create message as soon as possible

under non-OMEMO circumstances. Otherwise, when messages are fetched in
bulk via MAM, then a message referring to a previous one (e.g.
a correction) may be processed before the message being referred to has
been created.
parent 9a7f56db
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
<link rel="shortcut icon" type="image/ico" href="css/images/favicon.ico"/> <link rel="shortcut icon" type="image/ico" href="css/images/favicon.ico"/>
<link type="text/css" rel="stylesheet" media="screen" href="css/fullpage.css" /> <link type="text/css" rel="stylesheet" media="screen" href="css/fullpage.css" />
<link type="text/css" rel="stylesheet" media="screen" href="css/converse.css" /> <link type="text/css" rel="stylesheet" media="screen" href="css/converse.css" />
<script src="3rdparty/libsignal-protocol-javascript/dist/libsignal-protocol.js"></script>
<script src="dist/converse.js"></script> <script src="dist/converse.js"></script>
</head> </head>
......
...@@ -63111,29 +63111,39 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ ...@@ -63111,29 +63111,39 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
attrs.spoiler_hint = spoiler.textContent.length > 0 ? spoiler.textContent : ''; attrs.spoiler_hint = spoiler.textContent.length > 0 ? spoiler.textContent : '';
} }
return Promise.resolve(attrs); return attrs;
}, },
createMessage(message, original_stanza) { createMessage(message, original_stanza) {
/* Create a Backbone.Message object inside this chat box /* Create a Backbone.Message object inside this chat box
* based on the identified message stanza. * based on the identified message stanza.
*/ */
return new Promise((resolve, reject) => { const that = this;
this.getMessageAttributesFromStanza(message, original_stanza).then(attrs => {
const is_csn = u.isOnlyChatStateNotification(attrs);
if (is_csn && (attrs.is_delayed || attrs.type === 'groupchat' && Strophe.getResourceFromJid(attrs.from) == this.get('nick'))) { function _create(attrs) {
// XXX: MUC leakage const is_csn = u.isOnlyChatStateNotification(attrs);
// No need showing delayed or our own CSN messages
resolve(); if (is_csn && (attrs.is_delayed || attrs.type === 'groupchat' && Strophe.getResourceFromJid(attrs.from) == that.get('nick'))) {
} else if (!is_csn && !attrs.file && !attrs.message && !attrs.oob_url && attrs.type !== 'error') { // XXX: MUC leakage
// TODO: handle <subject> messages (currently being done by ChatRoom) // No need showing delayed or our own CSN messages
resolve(); return;
} else { } else if (!is_csn && !attrs.file && !attrs.message && !attrs.oob_url && attrs.type !== 'error') {
resolve(this.messages.create(attrs)); // TODO: handle <subject> messages (currently being done by ChatRoom)
} return;
}).catch(e => reject(e)); } else {
}); return that.messages.create(attrs);
}
}
const result = this.getMessageAttributesFromStanza(message, original_stanza);
if (result instanceof Promise) {
return new Promise((resolve, reject) => result.then(attrs => resolve(_create(attrs))).catch(reject));
} else {
const message = _create(result);
return Promise.resolve(message);
}
}, },
isHidden() { isHidden() {
...@@ -68657,17 +68667,23 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ ...@@ -68657,17 +68667,23 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
// New functions which don't exist yet can also be added. // New functions which don't exist yet can also be added.
ChatBox: { ChatBox: {
getMessageAttributesFromStanza(message, original_stanza) { getMessageAttributesFromStanza(message, original_stanza) {
return new Promise((resolve, reject) => { function _process(attrs) {
this.__super__.getMessageAttributesFromStanza.apply(this, arguments).then(attrs => { const archive_id = getMessageArchiveID(original_stanza);
const archive_id = getMessageArchiveID(original_stanza);
if (archive_id) { if (archive_id) {
attrs.archive_id = archive_id; attrs.archive_id = archive_id;
} }
resolve(attrs); return attrs;
}).catch(reject); }
});
const result = this.__super__.getMessageAttributesFromStanza.apply(this, arguments);
if (result instanceof Promise) {
return new Promise((resolve, reject) => result.then(attrs => resolve(_process(attrs))).catch(reject));
} else {
return _process(result);
}
} }
}, },
...@@ -74164,38 +74180,38 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ ...@@ -74164,38 +74180,38 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
}); });
}, },
getEncryptionAttributesfromStanza(stanza, original_stanza) { getEncryptionAttributesfromStanza(stanza, original_stanza, attrs) {
const _converse = this.__super__._converse; const _converse = this.__super__._converse,
const encrypted = sizzle(`encrypted[xmlns="${Strophe.NS.OMEMO}"]`, original_stanza).pop(); encrypted = sizzle(`encrypted[xmlns="${Strophe.NS.OMEMO}"]`, original_stanza).pop();
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.__super__.getMessageAttributesFromStanza.apply(this, arguments).then(attrs => { const _converse = this.__super__._converse,
const _converse = this.__super__._converse, header = encrypted.querySelector('header'),
header = encrypted.querySelector('header'), key = sizzle(`key[rid="${_converse.omemo_store.get('device_id')}"]`, encrypted).pop();
key = sizzle(`key[rid="${_converse.omemo_store.get('device_id')}"]`, encrypted).pop();
if (key) {
if (key) { attrs['encrypted'] = {
attrs['encrypted'] = { 'device_id': header.getAttribute('sid'),
'device_id': header.getAttribute('sid'), 'iv': header.querySelector('iv').textContent,
'iv': header.querySelector('iv').textContent, 'key': key.textContent,
'key': key.textContent, 'payload': _.get(encrypted.querySelector('payload'), 'textContent', null),
'payload': _.get(encrypted.querySelector('payload'), 'textContent', null), 'prekey': key.getAttribute('prekey')
'prekey': key.getAttribute('prekey') };
}; this.decrypt(attrs).then(plaintext => resolve(_.extend(attrs, {
this.decrypt(attrs).then(plaintext => resolve(_.extend(attrs, { 'plaintext': plaintext
'plaintext': plaintext }))).catch(reject);
}))).catch(reject); }
}
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
}); });
}, },
getMessageAttributesFromStanza(stanza, original_stanza) { getMessageAttributesFromStanza(stanza, original_stanza) {
const encrypted = sizzle(`encrypted[xmlns="${Strophe.NS.OMEMO}"]`, original_stanza).pop(); const encrypted = sizzle(`encrypted[xmlns="${Strophe.NS.OMEMO}"]`, original_stanza).pop();
const attrs = this.__super__.getMessageAttributesFromStanza.apply(this, arguments);
if (!encrypted) { if (!encrypted) {
return this.__super__.getMessageAttributesFromStanza.apply(this, arguments); return attrs;
} else { } else {
return this.getEncryptionAttributesfromStanza(stanza, original_stanza); return this.getEncryptionAttributesfromStanza(stanza, original_stanza, attrs);
} }
}, },
...@@ -74442,8 +74458,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ ...@@ -74442,8 +74458,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
'identity_keypair': identity_keypair, 'identity_keypair': identity_keypair,
'prekeys': {} 'prekeys': {}
}; };
const signed_prekey_id = '0'; libsignal.KeyHelper.generateSignedPreKey(identity_keypair, 0).then(signed_prekey => {
libsignal.KeyHelper.generateSignedPreKey(identity_keypair, signed_prekey_id).then(signed_prekey => {
data['signed_prekey'] = signed_prekey; data['signed_prekey'] = signed_prekey;
const key_promises = _.map(_.range(0, _converse.NUM_PREKEYS), id => libsignal.KeyHelper.generatePreKey(id)); const key_promises = _.map(_.range(0, _converse.NUM_PREKEYS), id => libsignal.KeyHelper.generatePreKey(id));
...@@ -74690,13 +74705,14 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ ...@@ -74690,13 +74705,14 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
'type': 'get', 'type': 'get',
'from': _converse.bare_jid, 'from': _converse.bare_jid,
'to': this.get('jid') 'to': this.get('jid')
}).c('query', { }).c('pubsub', {
'xmlns': Strophe.NS.DISCO_ITEMS, 'xmlns': Strophe.NS.PUBSUB
}).c('items', {
'node': Strophe.NS.OMEMO_DEVICELIST 'node': Strophe.NS.OMEMO_DEVICELIST
}); });
_converse.connection.sendIQ(stanza, iq => { _converse.connection.sendIQ(stanza, iq => {
_.forEach(iq.querySelectorAll('device'), dev => this.devices.create({ _.forEach(sizzle(`list[xmlns="${Strophe.NS.OMEMO}"] device`, iq), dev => this.devices.create({
'id': dev.getAttribute('id'), 'id': dev.getAttribute('id'),
'jid': this.get('jid') 'jid': this.get('jid')
})); }));
...@@ -74725,7 +74741,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ ...@@ -74725,7 +74741,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
'node': Strophe.NS.OMEMO_DEVICELIST 'node': Strophe.NS.OMEMO_DEVICELIST
}).c('item').c('list', { }).c('item').c('list', {
'xmlns': Strophe.NS.OMEMO 'xmlns': Strophe.NS.OMEMO
}).up(); });
_.each(this.devices.where({ _.each(this.devices.where({
'active': true 'active': true
...@@ -74905,7 +74921,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ ...@@ -74905,7 +74921,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_
fetchOwnDevices().then(() => restoreOMEMOSession()).then(() => updateOwnDeviceList()).then(() => _converse.omemo.publishBundle()).then(() => _converse.emit('OMEMOInitialized')).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR)); fetchOwnDevices().then(() => restoreOMEMOSession()).then(() => updateOwnDeviceList()).then(() => _converse.omemo.publishBundle()).then(() => _converse.emit('OMEMOInitialized')).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
} }
_converse.api.listen.on('afterTearDown', () => _converse.devices.reset()); _converse.api.listen.on('afterTearDown', () => _converse.devicelists.reset());
_converse.api.listen.on('connected', registerPEPPushHandler); _converse.api.listen.on('connected', registerPEPPushHandler);
...@@ -612,14 +612,10 @@ ...@@ -612,14 +612,10 @@
null, ['rosterGroupsFetched'], {}, null, ['rosterGroupsFetched'], {},
function (done, _converse) { function (done, _converse) {
let view, chat_content, $chat_content; test_utils.openAndEnterChatRoom(_converse, 'coven', 'chat.shakespeare.lit', 'dummy').then(function () {
const ONE_DAY_LATER = 86400000; var view = _converse.chatboxviews.get('coven@chat.shakespeare.lit');
const baseTime = new Date(); var chat_content = view.el.querySelector('.chat-content');
test_utils.openAndEnterChatRoom(_converse, 'coven', 'chat.shakespeare.lit', 'dummy') var $chat_content = $(chat_content);
.then(() => {
view = _converse.chatboxviews.get('coven@chat.shakespeare.lit');
chat_content = view.el.querySelector('.chat-content');
$chat_content = $(chat_content);
var indicator = chat_content.querySelector('.date-separator'); var indicator = chat_content.querySelector('.date-separator');
expect(indicator).not.toBe(null); expect(indicator).not.toBe(null);
expect(indicator.getAttribute('class')).toEqual('message date-separator'); expect(indicator.getAttribute('class')).toEqual('message date-separator');
...@@ -629,8 +625,11 @@ ...@@ -629,8 +625,11 @@
expect(chat_content.querySelector('div.chat-info').textContent).toBe( expect(chat_content.querySelector('div.chat-info').textContent).toBe(
"dummy has entered the groupchat" "dummy has entered the groupchat"
); );
var baseTime = new Date();
jasmine.clock().install(); jasmine.clock().install();
jasmine.clock().mockDate(baseTime); jasmine.clock().mockDate(baseTime);
var ONE_DAY_LATER = 86400000;
jasmine.clock().tick(ONE_DAY_LATER); jasmine.clock().tick(ONE_DAY_LATER);
/* <presence to="dummy@localhost/_converse.js-29092160" /* <presence to="dummy@localhost/_converse.js-29092160"
...@@ -695,7 +694,7 @@ ...@@ -695,7 +694,7 @@
jasmine.clock().tick(ONE_DAY_LATER); jasmine.clock().tick(ONE_DAY_LATER);
const stanza = Strophe.xmlHtmlNode( var stanza = Strophe.xmlHtmlNode(
'<message xmlns="jabber:client"' + '<message xmlns="jabber:client"' +
' to="dummy@localhost/_converse.js-290929789"' + ' to="dummy@localhost/_converse.js-290929789"' +
' type="groupchat"' + ' type="groupchat"' +
...@@ -704,15 +703,8 @@ ...@@ -704,15 +703,8 @@
' <delay xmlns="urn:xmpp:delay" stamp="'+moment().format()+'" from="some1@localhost"/>'+ ' <delay xmlns="urn:xmpp:delay" stamp="'+moment().format()+'" from="some1@localhost"/>'+
'</message>').firstChild; '</message>').firstChild;
_converse.connection._dataRecv(test_utils.createRequest(stanza)); _converse.connection._dataRecv(test_utils.createRequest(stanza));
jasmine.clock().uninstall();
return test_utils.waitUntil(() => view.model.messages.length); presence = $pres({
}).then(() => {
jasmine.clock().install();
jasmine.clock().mockDate(baseTime);
jasmine.clock().tick(ONE_DAY_LATER);
jasmine.clock().tick(ONE_DAY_LATER);
jasmine.clock().tick(ONE_DAY_LATER);
const presence = $pres({
to: 'dummy@localhost/_converse.js-29092160', to: 'dummy@localhost/_converse.js-29092160',
from: 'coven@chat.shakespeare.lit/newguy' from: 'coven@chat.shakespeare.lit/newguy'
}).c('x', {xmlns: Strophe.NS.MUC_USER}) }).c('x', {xmlns: Strophe.NS.MUC_USER})
...@@ -723,7 +715,7 @@ ...@@ -723,7 +715,7 @@
}); });
_converse.connection._dataRecv(test_utils.createRequest(presence)); _converse.connection._dataRecv(test_utils.createRequest(presence));
const time = chat_content.querySelectorAll('time.separator-text'); let time = chat_content.querySelectorAll('time.separator-text');
expect(time.length).toEqual(4); expect(time.length).toEqual(4);
var $indicator = $chat_content.find('.date-separator:eq(3)'); var $indicator = $chat_content.find('.date-separator:eq(3)');
...@@ -735,7 +727,7 @@ ...@@ -735,7 +727,7 @@
jasmine.clock().tick(ONE_DAY_LATER); jasmine.clock().tick(ONE_DAY_LATER);
const stanza = Strophe.xmlHtmlNode( stanza = Strophe.xmlHtmlNode(
'<message xmlns="jabber:client"' + '<message xmlns="jabber:client"' +
' to="dummy@localhost/_converse.js-290929789"' + ' to="dummy@localhost/_converse.js-290929789"' +
' type="groupchat"' + ' type="groupchat"' +
...@@ -744,18 +736,10 @@ ...@@ -744,18 +736,10 @@
' <delay xmlns="urn:xmpp:delay" stamp="'+moment().format()+'" from="some1@localhost"/>'+ ' <delay xmlns="urn:xmpp:delay" stamp="'+moment().format()+'" from="some1@localhost"/>'+
'</message>').firstChild; '</message>').firstChild;
_converse.connection._dataRecv(test_utils.createRequest(stanza)); _converse.connection._dataRecv(test_utils.createRequest(stanza));
jasmine.clock().uninstall();
return test_utils.waitUntil(() => view.model.messages.length > 1);
}).then(() => {
jasmine.clock().install();
jasmine.clock().mockDate(baseTime);
jasmine.clock().tick(ONE_DAY_LATER);
jasmine.clock().tick(ONE_DAY_LATER);
jasmine.clock().tick(ONE_DAY_LATER);
jasmine.clock().tick(ONE_DAY_LATER);
jasmine.clock().tick(ONE_DAY_LATER); jasmine.clock().tick(ONE_DAY_LATER);
// Test a user leaving a groupchat // Test a user leaving a groupchat
const presence = $pres({ presence = $pres({
to: 'dummy@localhost/_converse.js-29092160', to: 'dummy@localhost/_converse.js-29092160',
type: 'unavailable', type: 'unavailable',
from: 'coven@chat.shakespeare.lit/newguy' from: 'coven@chat.shakespeare.lit/newguy'
...@@ -769,10 +753,10 @@ ...@@ -769,10 +753,10 @@
}); });
_converse.connection._dataRecv(test_utils.createRequest(presence)); _converse.connection._dataRecv(test_utils.createRequest(presence));
const time = chat_content.querySelectorAll('time.separator-text'); time = chat_content.querySelectorAll('time.separator-text');
expect(time.length).toEqual(6); expect(time.length).toEqual(6);
const $indicator = $chat_content.find('.date-separator:eq(5)'); $indicator = $chat_content.find('.date-separator:eq(5)');
expect($indicator.attr('class')).toEqual('message date-separator'); expect($indicator.attr('class')).toEqual('message date-separator');
expect($indicator.data('isodate')).toEqual(moment().startOf('day').format()); expect($indicator.data('isodate')).toEqual(moment().startOf('day').format());
...@@ -849,7 +833,7 @@ ...@@ -849,7 +833,7 @@
}).then(() => { }).then(() => {
view = _converse.chatboxviews.get('lounge@localhost'); view = _converse.chatboxviews.get('lounge@localhost');
if (!$(view.el).find('.chat-area').length) { view.renderChatArea(); } if (!$(view.el).find('.chat-area').length) { view.renderChatArea(); }
const message = '/me is tired'; var message = '/me is tired';
var nick = mock.chatroom_names[0], var nick = mock.chatroom_names[0],
msg = $msg({ msg = $msg({
'from': 'lounge@localhost/'+nick, 'from': 'lounge@localhost/'+nick,
...@@ -858,21 +842,17 @@ ...@@ -858,21 +842,17 @@
'type': 'groupchat' 'type': 'groupchat'
}).c('body').t(message).tree(); }).c('body').t(message).tree();
view.model.onMessage(msg); view.model.onMessage(msg);
return test_utils.waitUntil(() => view.model.messages.length);
}).then(() => {
expect(_.includes($(view.el).find('.chat-msg__author').text(), '**Dyon van de Wege')).toBeTruthy(); expect(_.includes($(view.el).find('.chat-msg__author').text(), '**Dyon van de Wege')).toBeTruthy();
expect($(view.el).find('.chat-msg__text').text()).toBe(' is tired'); expect($(view.el).find('.chat-msg__text').text()).toBe(' is tired');
const message = '/me is as well'; message = '/me is as well';
const msg = $msg({ msg = $msg({
from: 'lounge@localhost/Max Mustermann', from: 'lounge@localhost/Max Mustermann',
id: (new Date()).getTime(), id: (new Date()).getTime(),
to: 'dummy@localhost', to: 'dummy@localhost',
type: 'groupchat' type: 'groupchat'
}).c('body').t(message).tree(); }).c('body').t(message).tree();
view.model.onMessage(msg); view.model.onMessage(msg);
return test_utils.waitUntil(() => view.model.messages.length > 1);
}).then(() => {
expect(_.includes($(view.el).find('.chat-msg__author:last').text(), '**Max Mustermann')).toBeTruthy(); expect(_.includes($(view.el).find('.chat-msg__author:last').text(), '**Max Mustermann')).toBeTruthy();
expect($(view.el).find('.chat-msg__text:last').text()).toBe(' is as well'); expect($(view.el).find('.chat-msg__text:last').text()).toBe(' is as well');
done(); done();
...@@ -1531,8 +1511,6 @@ ...@@ -1531,8 +1511,6 @@
type: 'groupchat' type: 'groupchat'
}).c('body').t(text); }).c('body').t(text);
view.model.onMessage(message.nodeTree); view.model.onMessage(message.nodeTree);
return test_utils.waitUntil(() => view.model.messages.length);
}).then(() => {
var $chat_content = $(view.el).find('.chat-content'); var $chat_content = $(view.el).find('.chat-content');
expect($chat_content.find('.chat-msg').length).toBe(1); expect($chat_content.find('.chat-msg').length).toBe(1);
expect($chat_content.find('.chat-msg__text').text()).toBe(text); expect($chat_content.find('.chat-msg__text').text()).toBe(text);
...@@ -1585,10 +1563,9 @@ ...@@ -1585,10 +1563,9 @@
null, ['rosterGroupsFetched'], {}, null, ['rosterGroupsFetched'], {},
function (done, _converse) { function (done, _converse) {
let view;
var message = 'This message is received while the chat area is scrolled up'; var message = 'This message is received while the chat area is scrolled up';
test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy').then(function () { test_utils.openAndEnterChatRoom(_converse, 'lounge', 'localhost', 'dummy').then(function () {
view = _converse.chatboxviews.get('lounge@localhost'); var view = _converse.chatboxviews.get('lounge@localhost');
spyOn(view, 'scrollDown').and.callThrough(); spyOn(view, 'scrollDown').and.callThrough();
/* Create enough messages so that there's a /* Create enough messages so that there's a
* scrollbar. * scrollbar.
...@@ -1602,8 +1579,6 @@ ...@@ -1602,8 +1579,6 @@
id: (new Date()).getTime(), id: (new Date()).getTime(),
}).c('body').t('Message: '+i).tree()); }).c('body').t('Message: '+i).tree());
} }
return test_utils.waitUntil(() => view.model.messages.length === 20);
}).then(() => {
// Give enough time for `markScrolled` to have been called // Give enough time for `markScrolled` to have been called
setTimeout(() => { setTimeout(() => {
view.content.scrollTop = 0; view.content.scrollTop = 0;
...@@ -1615,15 +1590,12 @@ ...@@ -1615,15 +1590,12 @@
id: (new Date()).getTime(), id: (new Date()).getTime(),
}).c('body').t(message).tree()); }).c('body').t(message).tree());
test_utils.waitUntil(() => view.model.messages.length === 21) // Now check that the message appears inside the chatbox in the DOM
.then(() => { var $chat_content = $(view.el).find('.chat-content');
// Now check that the message appears inside the chatbox in the DOM var msg_txt = $chat_content.find('.chat-msg:last').find('.chat-msg__text').text();
var $chat_content = $(view.el).find('.chat-content'); expect(msg_txt).toEqual(message);
var msg_txt = $chat_content.find('.chat-msg:last').find('.chat-msg__text').text(); expect(view.content.scrollTop).toBe(0);
expect(msg_txt).toEqual(message); done();
expect(view.content.scrollTop).toBe(0);
done();
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL))
}, 500); }, 500);
}); });
})); }));
...@@ -3511,11 +3483,11 @@ ...@@ -3511,11 +3483,11 @@
null, ['rosterGroupsFetched'], {}, null, ['rosterGroupsFetched'], {},
function (done, _converse) { function (done, _converse) {
let view; test_utils.openAndEnterChatRoom(
const room_jid = 'coven@chat.shakespeare.lit'; _converse, 'coven', 'chat.shakespeare.lit', 'some1').then(function () {
test_utils.openAndEnterChatRoom(_converse, 'coven', 'chat.shakespeare.lit', 'some1')
.then(() => { var room_jid = 'coven@chat.shakespeare.lit';
view = _converse.chatboxviews.get('coven@chat.shakespeare.lit'); var view = _converse.chatboxviews.get('coven@chat.shakespeare.lit');
var $chat_content = $(view.el).find('.chat-content'); var $chat_content = $(view.el).find('.chat-content');
expect($chat_content.find('div.chat-info:first').html()).toBe("some1 has entered the groupchat"); expect($chat_content.find('div.chat-info:first').html()).toBe("some1 has entered the groupchat");
...@@ -3557,9 +3529,9 @@ ...@@ -3557,9 +3529,9 @@
to: 'dummy@localhost', to: 'dummy@localhost',
type: 'groupchat' type: 'groupchat'
}).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree(); }).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
view.model.onMessage(msg); view.model.onMessage(msg);
return test_utils.waitUntil(() => view.model.messages.length);
}).then(() => {
// Check that the notification appears inside the chatbox in the DOM // Check that the notification appears inside the chatbox in the DOM
var events = view.el.querySelectorAll('.chat-event'); var events = view.el.querySelectorAll('.chat-event');
expect(events.length).toBe(3); expect(events.length).toBe(3);
...@@ -3571,61 +3543,89 @@ ...@@ -3571,61 +3543,89 @@
expect(notifications.length).toBe(1); expect(notifications.length).toBe(1);
expect(notifications[0].textContent).toEqual('newguy is typing'); expect(notifications[0].textContent).toEqual('newguy is typing');
const timeout_functions = [];
spyOn(window, 'setTimeout').and.callFake(function (func, delay) {
timeout_functions.push(func);
});
// Check that it doesn't appear twice // Check that it doesn't appear twice
const msg = $msg({ msg = $msg({
from: room_jid+'/newguy', from: room_jid+'/newguy',
id: (new Date()).getTime(), id: (new Date()).getTime(),
to: 'dummy@localhost', to: 'dummy@localhost',
type: 'groupchat' type: 'groupchat'
}).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree(); }).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
view.model.onMessage(msg); view.model.onMessage(msg);
return test_utils.waitUntil(() => view.model.messages.length > 1);
}).then(() => { events = view.el.querySelectorAll('.chat-event');
const events = view.el.querySelectorAll('.chat-event');
expect(events.length).toBe(3); expect(events.length).toBe(3);
expect(events[0].textContent).toEqual('some1 has entered the groupchat'); expect(events[0].textContent).toEqual('some1 has entered the groupchat');
expect(events[1].textContent).toEqual('newguy has entered the groupchat'); expect(events[1].textContent).toEqual('newguy has entered the groupchat');
expect(events[2].textContent).toEqual('nomorenicks has entered the groupchat'); expect(events[2].textContent).toEqual('nomorenicks has entered the groupchat');
const notifications = view.el.querySelectorAll('.chat-state-notification'); notifications = view.el.querySelectorAll('.chat-state-notification');
expect(notifications.length).toBe(1); expect(notifications.length).toBe(1);
expect(notifications[0].textContent).toEqual('newguy is typing'); expect(notifications[0].textContent).toEqual('newguy is typing');
expect(timeout_functions.length).toBe(1);
// <composing> state for a different occupant // <composing> state for a different occupant
const msg = $msg({ msg = $msg({
from: room_jid+'/nomorenicks', from: room_jid+'/nomorenicks',
id: (new Date()).getTime(), id: (new Date()).getTime(),
to: 'dummy@localhost', to: 'dummy@localhost',
type: 'groupchat' type: 'groupchat'
}).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree(); }).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
view.model.onMessage(msg); view.model.onMessage(msg);
return test_utils.waitUntil(() => view.model.messages.length > 2); events = view.el.querySelectorAll('.chat-event');
}).then(() => {
const events = view.el.querySelectorAll('.chat-event');
expect(events.length).toBe(3); expect(events.length).toBe(3);
expect(events[0].textContent).toEqual('some1 has entered the groupchat'); expect(events[0].textContent).toEqual('some1 has entered the groupchat');
expect(events[1].textContent).toEqual('newguy has entered the groupchat'); expect(events[1].textContent).toEqual('newguy has entered the groupchat');
expect(events[2].textContent).toEqual('nomorenicks has entered the groupchat'); expect(events[2].textContent).toEqual('nomorenicks has entered the groupchat');
const notifications = view.el.querySelectorAll('.chat-state-notification'); notifications = view.el.querySelectorAll('.chat-state-notification');
expect(notifications.length).toBe(2); expect(notifications.length).toBe(2);
expect(notifications[0].textContent).toEqual('newguy is typing'); expect(notifications[0].textContent).toEqual('newguy is typing');
expect(notifications[1].textContent).toEqual('nomorenicks is typing'); expect(notifications[1].textContent).toEqual('nomorenicks is typing');
expect(timeout_functions.length).toBe(2);
// Check that new messages appear under the chat state // Check that new messages appear under the chat state
// notifications // notifications
const msg = $msg({ msg = $msg({
from: 'lounge@localhost/some1', from: 'lounge@localhost/some1',
id: (new Date()).getTime(), id: (new Date()).getTime(),
to: 'dummy@localhost', to: 'dummy@localhost',
type: 'groupchat' type: 'groupchat'
}).c('body').t('hello world').tree(); }).c('body').t('hello world').tree();
view.model.onMessage(msg); view.model.onMessage(msg);
return test_utils.waitUntil(() => view.model.messages.length);
}).then(() => { var messages = view.el.querySelectorAll('.message');
const messages = view.el.querySelectorAll('.message');
expect(messages.length).toBe(7); expect(messages.length).toBe(7);
expect(view.el.querySelectorAll('.chat-msg').length).toBe(1); expect(view.el.querySelectorAll('.chat-msg').length).toBe(1);
expect(view.el.querySelector('.chat-msg .chat-msg__text').textContent).toBe('hello world'); expect(view.el.querySelector('.chat-msg .chat-msg__text').textContent).toBe('hello world');
// Test that the composing notifications get removed
// via timeout.
timeout_functions[0]();
events = view.el.querySelectorAll('.chat-event');
expect(events.length).toBe(3);
expect(events[0].textContent).toEqual('some1 has entered the groupchat');
expect(events[1].textContent).toEqual('newguy has entered the groupchat');
expect(events[2].textContent).toEqual('nomorenicks has entered the groupchat');
notifications = view.el.querySelectorAll('.chat-state-notification');
expect(notifications.length).toBe(1);
expect(notifications[0].textContent).toEqual('nomorenicks is typing');
timeout_functions[1]();
events = view.el.querySelectorAll('.chat-event');
expect(events.length).toBe(3);
expect(events[0].textContent).toEqual('some1 has entered the groupchat');
expect(events[1].textContent).toEqual('newguy has entered the groupchat');
expect(events[2].textContent).toEqual('nomorenicks has entered the groupchat');
notifications = view.el.querySelectorAll('.chat-state-notification');
expect(notifications.length).toBe(0);
done(); done();
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL)) }).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL))
})); }));
...@@ -3637,11 +3637,10 @@ ...@@ -3637,11 +3637,10 @@
null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
function (done, _converse) { function (done, _converse) {
let view, msg
const room_jid = 'coven@chat.shakespeare.lit';
test_utils.openChatRoom(_converse, "coven", 'chat.shakespeare.lit', 'some1') test_utils.openChatRoom(_converse, "coven", 'chat.shakespeare.lit', 'some1')
.then(() => { .then(() => {
view = _converse.chatboxviews.get('coven@chat.shakespeare.lit'); var room_jid = 'coven@chat.shakespeare.lit';
var view = _converse.chatboxviews.get('coven@chat.shakespeare.lit');
var $chat_content = $(view.el).find('.chat-content'); var $chat_content = $(view.el).find('.chat-content');
/* <presence to="dummy@localhost/_converse.js-29092160" /* <presence to="dummy@localhost/_converse.js-29092160"
...@@ -3696,15 +3695,13 @@ ...@@ -3696,15 +3695,13 @@
// See XEP-0085 http://xmpp.org/extensions/xep-0085.html#definitions // See XEP-0085 http://xmpp.org/extensions/xep-0085.html#definitions
// <composing> state // <composing> state
const msg = $msg({ var msg = $msg({
from: room_jid+'/newguy', from: room_jid+'/newguy',
id: (new Date()).getTime(), id: (new Date()).getTime(),
to: 'dummy@localhost', to: 'dummy@localhost',
type: 'groupchat' type: 'groupchat'
}).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree(); }).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
view.model.onMessage(msg); view.model.onMessage(msg);
return test_utils.waitUntil(() => view.model.messages.length);
}).then(() => {
// Check that the notification appears inside the chatbox in the DOM // Check that the notification appears inside the chatbox in the DOM
var events = view.el.querySelectorAll('.chat-event'); var events = view.el.querySelectorAll('.chat-event');
...@@ -3718,64 +3715,58 @@ ...@@ -3718,64 +3715,58 @@
expect(notifications[0].textContent).toEqual('newguy is typing'); expect(notifications[0].textContent).toEqual('newguy is typing');
// Check that it doesn't appear twice // Check that it doesn't appear twice
const msg = $msg({ msg = $msg({
from: room_jid+'/newguy', from: room_jid+'/newguy',
id: (new Date()).getTime(), id: (new Date()).getTime(),
to: 'dummy@localhost', to: 'dummy@localhost',
type: 'groupchat' type: 'groupchat'
}).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree(); }).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
view.model.onMessage(msg); view.model.onMessage(msg);
return test_utils.waitUntil(() => view.model.messages.length > 1);
}).then(() => {
const events = view.el.querySelectorAll('.chat-event'); events = view.el.querySelectorAll('.chat-event');
expect(events.length).toBe(3); expect(events.length).toBe(3);
expect(events[0].textContent).toEqual('some1 has entered the groupchat'); expect(events[0].textContent).toEqual('some1 has entered the groupchat');
expect(events[1].textContent).toEqual('newguy has entered the groupchat'); expect(events[1].textContent).toEqual('newguy has entered the groupchat');
expect(events[2].textContent).toEqual('nomorenicks has entered the groupchat'); expect(events[2].textContent).toEqual('nomorenicks has entered the groupchat');
const notifications = view.el.querySelectorAll('.chat-state-notification'); notifications = view.el.querySelectorAll('.chat-state-notification');
expect(notifications.length).toBe(1); expect(notifications.length).toBe(1);
expect(notifications[0].textContent).toEqual('newguy is typing'); expect(notifications[0].textContent).toEqual('newguy is typing');
// <composing> state for a different occupant // <composing> state for a different occupant
const msg = $msg({ msg = $msg({
from: room_jid+'/nomorenicks', from: room_jid+'/nomorenicks',
id: (new Date()).getTime(), id: (new Date()).getTime(),
to: 'dummy@localhost', to: 'dummy@localhost',
type: 'groupchat' type: 'groupchat'
}).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree(); }).c('body').c('composing', {'xmlns': Strophe.NS.CHATSTATES}).tree();
view.model.onMessage(msg); view.model.onMessage(msg);
return test_utils.waitUntil(() => view.model.messages.length > 2); events = view.el.querySelectorAll('.chat-event');
}).then(() => {
const events = view.el.querySelectorAll('.chat-event');
expect(events.length).toBe(3); expect(events.length).toBe(3);
expect(events[0].textContent).toEqual('some1 has entered the groupchat'); expect(events[0].textContent).toEqual('some1 has entered the groupchat');
expect(events[1].textContent).toEqual('newguy has entered the groupchat'); expect(events[1].textContent).toEqual('newguy has entered the groupchat');
expect(events[2].textContent).toEqual('nomorenicks has entered the groupchat'); expect(events[2].textContent).toEqual('nomorenicks has entered the groupchat');
const notifications = view.el.querySelectorAll('.chat-state-notification'); notifications = view.el.querySelectorAll('.chat-state-notification');
expect(notifications.length).toBe(2); expect(notifications.length).toBe(2);
expect(notifications[0].textContent).toEqual('newguy is typing'); expect(notifications[0].textContent).toEqual('newguy is typing');
expect(notifications[1].textContent).toEqual('nomorenicks is typing'); expect(notifications[1].textContent).toEqual('nomorenicks is typing');
// <paused> state from occupant who typed first // <paused> state from occupant who typed first
const msg = $msg({ msg = $msg({
from: room_jid+'/newguy', from: room_jid+'/newguy',
id: (new Date()).getTime(), id: (new Date()).getTime(),
to: 'dummy@localhost', to: 'dummy@localhost',
type: 'groupchat' type: 'groupchat'
}).c('body').c('paused', {'xmlns': Strophe.NS.CHATSTATES}).tree(); }).c('body').c('paused', {'xmlns': Strophe.NS.CHATSTATES}).tree();
view.model.onMessage(msg); view.model.onMessage(msg);
return test_utils.waitUntil(() => view.model.messages.length > 3); events = view.el.querySelectorAll('.chat-event');
}).then(() => {
const events = view.el.querySelectorAll('.chat-event');
expect(events.length).toBe(3); expect(events.length).toBe(3);
expect(events[0].textContent).toEqual('some1 has entered the groupchat'); expect(events[0].textContent).toEqual('some1 has entered the groupchat');
expect(events[1].textContent).toEqual('newguy has entered the groupchat'); expect(events[1].textContent).toEqual('newguy has entered the groupchat');
expect(events[2].textContent).toEqual('nomorenicks has entered the groupchat'); expect(events[2].textContent).toEqual('nomorenicks has entered the groupchat');
const notifications = view.el.querySelectorAll('.chat-state-notification'); notifications = view.el.querySelectorAll('.chat-state-notification');
expect(notifications.length).toBe(2); expect(notifications.length).toBe(2);
expect(notifications[0].textContent).toEqual('nomorenicks is typing'); expect(notifications[0].textContent).toEqual('nomorenicks is typing');
expect(notifications[1].textContent).toEqual('newguy has stopped typing'); expect(notifications[1].textContent).toEqual('newguy has stopped typing');
......
...@@ -529,29 +529,35 @@ ...@@ -529,29 +529,35 @@
if (spoiler) { if (spoiler) {
attrs.spoiler_hint = spoiler.textContent.length > 0 ? spoiler.textContent : ''; attrs.spoiler_hint = spoiler.textContent.length > 0 ? spoiler.textContent : '';
} }
return Promise.resolve(attrs); return attrs;
}, },
createMessage (message, original_stanza) { createMessage (message, original_stanza) {
/* Create a Backbone.Message object inside this chat box /* Create a Backbone.Message object inside this chat box
* based on the identified message stanza. * based on the identified message stanza.
*/ */
return new Promise((resolve, reject) => { const that = this;
this.getMessageAttributesFromStanza(message, original_stanza) function _create (attrs) {
.then((attrs) => { const is_csn = u.isOnlyChatStateNotification(attrs);
const is_csn = u.isOnlyChatStateNotification(attrs); if (is_csn && (attrs.is_delayed ||
if (is_csn && (attrs.is_delayed || (attrs.type === 'groupchat' && Strophe.getResourceFromJid(attrs.from) == this.get('nick')))) { (attrs.type === 'groupchat' && Strophe.getResourceFromJid(attrs.from) == that.get('nick')))) {
// XXX: MUC leakage // XXX: MUC leakage
// No need showing delayed or our own CSN messages // No need showing delayed or our own CSN messages
resolve(); return;
} else if (!is_csn && !attrs.file && !attrs.message && !attrs.oob_url && attrs.type !== 'error') { } else if (!is_csn && !attrs.file && !attrs.message && !attrs.oob_url && attrs.type !== 'error') {
// TODO: handle <subject> messages (currently being done by ChatRoom) // TODO: handle <subject> messages (currently being done by ChatRoom)
resolve(); return;
} else { } else {
resolve(this.messages.create(attrs)); return that.messages.create(attrs);
} }
}).catch(e => reject(e)) }
}); const result = this.getMessageAttributesFromStanza(message, original_stanza)
if (result instanceof Promise) {
return new Promise((resolve, reject) => result.then(attrs => resolve(_create(attrs))).catch(reject));
} else {
const message = _create(result)
return Promise.resolve(message);
}
}, },
isHidden () { isHidden () {
......
...@@ -130,16 +130,19 @@ ...@@ -130,16 +130,19 @@
ChatBox: { ChatBox: {
getMessageAttributesFromStanza (message, original_stanza) { getMessageAttributesFromStanza (message, original_stanza) {
return new Promise((resolve, reject) => { function _process (attrs) {
this.__super__.getMessageAttributesFromStanza.apply(this, arguments) const archive_id = getMessageArchiveID(original_stanza);
.then((attrs) => { if (archive_id) {
const archive_id = getMessageArchiveID(original_stanza); attrs.archive_id = archive_id;
if (archive_id) { }
attrs.archive_id = archive_id; return attrs;
} }
resolve(attrs); const result = this.__super__.getMessageAttributesFromStanza.apply(this, arguments)
}).catch(reject); if (result instanceof Promise) {
}); return new Promise((resolve, reject) => result.then((attrs) => resolve(_process(attrs))).catch(reject));
} else {
return _process(result);
}
} }
}, },
......
...@@ -203,39 +203,37 @@ ...@@ -203,39 +203,37 @@
}); });
}, },
getEncryptionAttributesfromStanza (stanza, original_stanza) { getEncryptionAttributesfromStanza (stanza, original_stanza, attrs) {
const { _converse } = this.__super__; const { _converse } = this.__super__,
const encrypted = sizzle(`encrypted[xmlns="${Strophe.NS.OMEMO}"]`, original_stanza).pop(); encrypted = sizzle(`encrypted[xmlns="${Strophe.NS.OMEMO}"]`, original_stanza).pop();
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.__super__.getMessageAttributesFromStanza.apply(this, arguments) const { _converse } = this.__super__,
.then((attrs) => { header = encrypted.querySelector('header'),
const { _converse } = this.__super__, key = sizzle(`key[rid="${_converse.omemo_store.get('device_id')}"]`, encrypted).pop();
header = encrypted.querySelector('header'),
key = sizzle(`key[rid="${_converse.omemo_store.get('device_id')}"]`, encrypted).pop(); if (key) {
attrs['encrypted'] = {
if (key) { 'device_id': header.getAttribute('sid'),
attrs['encrypted'] = { 'iv': header.querySelector('iv').textContent,
'device_id': header.getAttribute('sid'), 'key': key.textContent,
'iv': header.querySelector('iv').textContent, 'payload': _.get(encrypted.querySelector('payload'), 'textContent', null),
'key': key.textContent, 'prekey': key.getAttribute('prekey')
'payload': _.get(encrypted.querySelector('payload'), 'textContent', null),
'prekey': key.getAttribute('prekey')
}
this.decrypt(attrs)
.then((plaintext) => resolve(_.extend(attrs, {'plaintext': plaintext})))
.catch(reject);
} }
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR)); this.decrypt(attrs)
.then((plaintext) => resolve(_.extend(attrs, {'plaintext': plaintext})))
.catch(reject);
}
}); });
}, },
getMessageAttributesFromStanza (stanza, original_stanza) { getMessageAttributesFromStanza (stanza, original_stanza) {
const encrypted = sizzle(`encrypted[xmlns="${Strophe.NS.OMEMO}"]`, original_stanza).pop(); const encrypted = sizzle(`encrypted[xmlns="${Strophe.NS.OMEMO}"]`, original_stanza).pop();
const attrs = this.__super__.getMessageAttributesFromStanza.apply(this, arguments);
if (!encrypted) { if (!encrypted) {
return this.__super__.getMessageAttributesFromStanza.apply(this, arguments); return attrs;
} else { } else {
return this.getEncryptionAttributesfromStanza(stanza, original_stanza); return this.getEncryptionAttributesfromStanza(stanza, original_stanza, attrs);
} }
}, },
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment