Commit 439d13d3 authored by JC Brand's avatar JC Brand

Use async/await instead of promise callbacks

parent e181aaf9
...@@ -37,81 +37,71 @@ ...@@ -37,81 +37,71 @@
).pop(); ).pop();
} }
function initializedOMEMO (_converse) { async function initializedOMEMO (_converse) {
return test_utils.waitUntil(() => deviceListFetched(_converse, _converse.bare_jid)) let iq_stanza = await test_utils.waitUntil(() => deviceListFetched(_converse, _converse.bare_jid));
.then(iq_stanza => { let stanza = $iq({
const stanza = $iq({ 'from': _converse.bare_jid,
'from': _converse.bare_jid, 'id': iq_stanza.nodeTree.getAttribute('id'),
'id': iq_stanza.nodeTree.getAttribute('id'), 'to': _converse.bare_jid,
'to': _converse.bare_jid, 'type': 'result',
'type': 'result', }).c('pubsub', {'xmlns': "http://jabber.org/protocol/pubsub"})
}).c('pubsub', {'xmlns': "http://jabber.org/protocol/pubsub"}) .c('items', {'node': "eu.siacs.conversations.axolotl.devicelist"})
.c('items', {'node': "eu.siacs.conversations.axolotl.devicelist"}) .c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute
.c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute .c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
.c('list', {'xmlns': "eu.siacs.conversations.axolotl"}) .c('device', {'id': '482886413b977930064a5888b92134fe'});
.c('device', {'id': '482886413b977930064a5888b92134fe'}); _converse.connection._dataRecv(test_utils.createRequest(stanza));
_converse.connection._dataRecv(test_utils.createRequest(stanza)); iq_stanza = await test_utils.waitUntil(() => ownDeviceHasBeenPublished(_converse))
return test_utils.waitUntil(() => ownDeviceHasBeenPublished(_converse))
}).then(iq_stanza => { stanza = $iq({
const stanza = $iq({ 'from': _converse.bare_jid,
'from': _converse.bare_jid, 'id': iq_stanza.nodeTree.getAttribute('id'),
'id': iq_stanza.nodeTree.getAttribute('id'), 'to': _converse.bare_jid,
'to': _converse.bare_jid, 'type': 'result'});
'type': 'result'}); _converse.connection._dataRecv(test_utils.createRequest(stanza));
_converse.connection._dataRecv(test_utils.createRequest(stanza)); iq_stanza = await test_utils.waitUntil(() => bundleHasBeenPublished(_converse))
return test_utils.waitUntil(() => bundleHasBeenPublished(_converse))
}).then(iq_stanza => { stanza = $iq({
const stanza = $iq({ 'from': _converse.bare_jid,
'from': _converse.bare_jid, 'id': iq_stanza.nodeTree.getAttribute('id'),
'id': iq_stanza.nodeTree.getAttribute('id'), 'to': _converse.bare_jid,
'to': _converse.bare_jid, 'type': 'result'});
'type': 'result'}); _converse.connection._dataRecv(test_utils.createRequest(stanza));
_converse.connection._dataRecv(test_utils.createRequest(stanza)); await _converse.api.waitUntil('OMEMOInitialized');
return _converse.api.waitUntil('OMEMOInitialized');
});
} }
describe("The OMEMO module", function() { describe("The OMEMO module", function() {
it("adds methods for encrypting and decrypting messages via AES GCM", it("adds methods for encrypting and decrypting messages via AES GCM",
mock.initConverseWithPromises( mock.initConverseWithPromises(
null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
function (done, _converse) { async function (done, _converse) {
const message = 'This message will be encrypted' const message = 'This message will be encrypted'
let view;
test_utils.createContacts(_converse, 'current', 1); test_utils.createContacts(_converse, 'current', 1);
_converse.emit('rosterContactsFetched'); _converse.emit('rosterContactsFetched');
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
test_utils.openChatBoxFor(_converse, contact_jid) const view = await test_utils.openChatBoxFor(_converse, contact_jid);
.then((v) => { const payload = await view.model.encryptMessage(message);
view = v; const result = await view.model.decryptMessage(payload);
return view.model.encryptMessage(message); expect(result).toBe(message);
}).then(payload => { done();
return view.model.decryptMessage(payload);
}).then(result => {
expect(result).toBe(message);
done();
});
})); }));
it("enables encrypted messages to be sent and received", it("enables encrypted messages to be sent and received",
mock.initConverseWithPromises( mock.initConverseWithPromises(
null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
function (done, _converse) { async function (done, _converse) {
let view, sent_stanza; let sent_stanza;
test_utils.createContacts(_converse, 'current', 1); test_utils.createContacts(_converse, 'current', 1);
_converse.emit('rosterContactsFetched'); _converse.emit('rosterContactsFetched');
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
await test_utils.waitUntil(() => initializedOMEMO(_converse));
return test_utils.waitUntil(() => initializedOMEMO(_converse)) await test_utils.openChatBoxFor(_converse, contact_jid);
.then(() => test_utils.openChatBoxFor(_converse, contact_jid)) let iq_stanza = await test_utils.waitUntil(() => deviceListFetched(_converse, contact_jid));
.then(() => test_utils.waitUntil(() => deviceListFetched(_converse, contact_jid))) let stanza = $iq({
.then(iq_stanza => {
const stanza = $iq({
'from': contact_jid, 'from': contact_jid,
'id': iq_stanza.nodeTree.getAttribute('id'), 'id': iq_stanza.nodeTree.getAttribute('id'),
'to': _converse.connection.jid, 'to': _converse.connection.jid,
...@@ -121,120 +111,108 @@ ...@@ -121,120 +111,108 @@
.c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute .c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute
.c('list', {'xmlns': "eu.siacs.conversations.axolotl"}) .c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
.c('device', {'id': '555'}); .c('device', {'id': '555'});
_converse.connection._dataRecv(test_utils.createRequest(stanza)); _converse.connection._dataRecv(test_utils.createRequest(stanza));
return test_utils.waitUntil(() => _converse.omemo_store); await test_utils.waitUntil(() => _converse.omemo_store);
}).then(() => { const devicelist = _converse.devicelists.get({'jid': contact_jid});
const devicelist = _converse.devicelists.get({'jid': contact_jid}); expect(devicelist.devices.length).toBe(1);
expect(devicelist.devices.length).toBe(1);
const view = _converse.chatboxviews.get(contact_jid);
view = _converse.chatboxviews.get(contact_jid); view.model.set('omemo_active', true);
view.model.set('omemo_active', true);
const textarea = view.el.querySelector('.chat-textarea');
const textarea = view.el.querySelector('.chat-textarea'); textarea.value = 'This message will be encrypted';
textarea.value = 'This message will be encrypted'; view.keyPressed({
view.keyPressed({ target: textarea,
target: textarea, preventDefault: _.noop,
preventDefault: _.noop, keyCode: 13 // Enter
keyCode: 13 // Enter });
}); iq_stanza = await test_utils.waitUntil(() => bundleFetched(_converse, contact_jid, '555'));
return test_utils.waitUntil(() => bundleFetched(_converse, contact_jid, '555')); stanza = $iq({
}).then(iq_stanza => { 'from': contact_jid,
const stanza = $iq({ 'id': iq_stanza.nodeTree.getAttribute('id'),
'to': _converse.bare_jid,
'type': 'result',
}).c('pubsub', {
'xmlns': 'http://jabber.org/protocol/pubsub'
}).c('items', {'node': "eu.siacs.conversations.axolotl.bundles:555"})
.c('item')
.c('bundle', {'xmlns': 'eu.siacs.conversations.axolotl'})
.c('signedPreKeyPublic', {'signedPreKeyId': '4223'}).t(btoa('1111')).up()
.c('signedPreKeySignature').t(btoa('2222')).up()
.c('identityKey').t(btoa('3333')).up()
.c('prekeys')
.c('preKeyPublic', {'preKeyId': '1'}).t(btoa('1001')).up()
.c('preKeyPublic', {'preKeyId': '2'}).t(btoa('1002')).up()
.c('preKeyPublic', {'preKeyId': '3'}).t(btoa('1003'));
_converse.connection._dataRecv(test_utils.createRequest(stanza));
iq_stanza = await test_utils.waitUntil(() => bundleFetched(_converse, _converse.bare_jid, '482886413b977930064a5888b92134fe'));
stanza = $iq({
'from': _converse.bare_jid,
'id': iq_stanza.nodeTree.getAttribute('id'),
'to': _converse.bare_jid,
'type': 'result',
}).c('pubsub', {
'xmlns': 'http://jabber.org/protocol/pubsub'
}).c('items', {'node': "eu.siacs.conversations.axolotl.bundles:482886413b977930064a5888b92134fe"})
.c('item')
.c('bundle', {'xmlns': 'eu.siacs.conversations.axolotl'})
.c('signedPreKeyPublic', {'signedPreKeyId': '4223'}).t(btoa('100000')).up()
.c('signedPreKeySignature').t(btoa('200000')).up()
.c('identityKey').t(btoa('300000')).up()
.c('prekeys')
.c('preKeyPublic', {'preKeyId': '1'}).t(btoa('1991')).up()
.c('preKeyPublic', {'preKeyId': '2'}).t(btoa('1992')).up()
.c('preKeyPublic', {'preKeyId': '3'}).t(btoa('1993'));
spyOn(_converse.connection, 'send').and.callFake(stanza => { sent_stanza = stanza });
_converse.connection._dataRecv(test_utils.createRequest(stanza));
await test_utils.waitUntil(() => sent_stanza);
expect(sent_stanza.toLocaleString()).toBe(
`<message from="dummy@localhost/resource" id="${sent_stanza.nodeTree.getAttribute("id")}" `+
`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>`+
`<encrypted xmlns="eu.siacs.conversations.axolotl">`+
`<header sid="123456789">`+
`<key rid="482886413b977930064a5888b92134fe">YzFwaDNSNzNYNw==</key>`+
`<key rid="555">YzFwaDNSNzNYNw==</key>`+
`<iv>${sent_stanza.nodeTree.querySelector("iv").textContent}</iv>`+
`</header>`+
`<payload>${sent_stanza.nodeTree.querySelector("payload").textContent}</payload>`+
`</encrypted>`+
`<store xmlns="urn:xmpp:hints"/>`+
`</message>`);
// Test reception of an encrypted message
const obj = await view.model.encryptMessage('This is an encrypted message from the contact')
// XXX: Normally the key will be encrypted via libsignal.
// However, we're mocking libsignal in the tests, so we include
// it as plaintext in the message.
stanza = $msg({
'from': contact_jid, 'from': contact_jid,
'id': iq_stanza.nodeTree.getAttribute('id'), 'to': _converse.connection.jid,
'to': _converse.bare_jid, 'type': 'chat',
'type': 'result', 'id': 'qwerty'
}).c('pubsub', { }).c('body').t('This is a fallback message').up()
'xmlns': 'http://jabber.org/protocol/pubsub' .c('encrypted', {'xmlns': Strophe.NS.OMEMO})
}).c('items', {'node': "eu.siacs.conversations.axolotl.bundles:555"}) .c('header', {'sid': '555'})
.c('item') .c('key', {'rid': _converse.omemo_store.get('device_id')}).t(u.arrayBufferToBase64(obj.key_and_tag)).up()
.c('bundle', {'xmlns': 'eu.siacs.conversations.axolotl'}) .c('iv').t(obj.iv)
.c('signedPreKeyPublic', {'signedPreKeyId': '4223'}).t(btoa('1111')).up() .up().up()
.c('signedPreKeySignature').t(btoa('2222')).up() .c('payload').t(obj.payload);
.c('identityKey').t(btoa('3333')).up() _converse.connection._dataRecv(test_utils.createRequest(stanza));
.c('prekeys') await test_utils.waitUntil(() => view.model.messages.length > 1);
.c('preKeyPublic', {'preKeyId': '1'}).t(btoa('1001')).up() expect(view.model.messages.length).toBe(2);
.c('preKeyPublic', {'preKeyId': '2'}).t(btoa('1002')).up() const last_msg = view.model.messages.at(1);
.c('preKeyPublic', {'preKeyId': '3'}).t(btoa('1003')); expect(view.el.querySelectorAll('.chat-msg__body')[1].textContent.trim())
_converse.connection._dataRecv(test_utils.createRequest(stanza)); .toBe('This is an encrypted message from the contact');
done();
return test_utils.waitUntil(() => bundleFetched(_converse, _converse.bare_jid, '482886413b977930064a5888b92134fe'));
}).then(iq_stanza => {
const stanza = $iq({
'from': _converse.bare_jid,
'id': iq_stanza.nodeTree.getAttribute('id'),
'to': _converse.bare_jid,
'type': 'result',
}).c('pubsub', {
'xmlns': 'http://jabber.org/protocol/pubsub'
}).c('items', {'node': "eu.siacs.conversations.axolotl.bundles:482886413b977930064a5888b92134fe"})
.c('item')
.c('bundle', {'xmlns': 'eu.siacs.conversations.axolotl'})
.c('signedPreKeyPublic', {'signedPreKeyId': '4223'}).t(btoa('100000')).up()
.c('signedPreKeySignature').t(btoa('200000')).up()
.c('identityKey').t(btoa('300000')).up()
.c('prekeys')
.c('preKeyPublic', {'preKeyId': '1'}).t(btoa('1991')).up()
.c('preKeyPublic', {'preKeyId': '2'}).t(btoa('1992')).up()
.c('preKeyPublic', {'preKeyId': '3'}).t(btoa('1993'));
spyOn(_converse.connection, 'send').and.callFake(stanza => { sent_stanza = stanza });
_converse.connection._dataRecv(test_utils.createRequest(stanza));
return test_utils.waitUntil(() => sent_stanza);
}).then(() => {
expect(sent_stanza.toLocaleString()).toBe(
`<message from="dummy@localhost/resource" id="${sent_stanza.nodeTree.getAttribute("id")}" `+
`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>`+
`<encrypted xmlns="eu.siacs.conversations.axolotl">`+
`<header sid="123456789">`+
`<key rid="482886413b977930064a5888b92134fe">YzFwaDNSNzNYNw==</key>`+
`<key rid="555">YzFwaDNSNzNYNw==</key>`+
`<iv>${sent_stanza.nodeTree.querySelector("iv").textContent}</iv>`+
`</header>`+
`<payload>${sent_stanza.nodeTree.querySelector("payload").textContent}</payload>`+
`</encrypted>`+
`<store xmlns="urn:xmpp:hints"/>`+
`</message>`);
// Test reception of an encrypted message
return view.model.encryptMessage('This is an encrypted message from the contact')
}).then(obj => {
// XXX: Normally the key will be encrypted via libsignal.
// However, we're mocking libsignal in the tests, so we include
// it as plaintext in the message.
// u.stringToArrayBuffer(atob(u.arrayBufferToBase64(obj.key_and_tag)));
// u.stringToArrayBuffer(u.arrayBufferToString(obj.key_and_tag));
const stanza = $msg({
'from': contact_jid,
'to': _converse.connection.jid,
'type': 'chat',
'id': 'qwerty'
}).c('body').t('This is a fallback message').up()
.c('encrypted', {'xmlns': Strophe.NS.OMEMO})
.c('header', {'sid': '555'})
.c('key', {'rid': _converse.omemo_store.get('device_id')}).t(u.arrayBufferToBase64(obj.key_and_tag)).up()
.c('iv').t(obj.iv)
.up().up()
.c('payload').t(obj.payload);
_converse.connection._dataRecv(test_utils.createRequest(stanza));
return test_utils.waitUntil(() => view.model.messages.length > 1);
}).then(() => {
expect(view.model.messages.length).toBe(2);
const last_msg = view.model.messages.at(1);
expect(view.el.querySelectorAll('.chat-msg__body')[1].textContent.trim())
.toBe('This is an encrypted message from the contact');
done();
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL))
})); }));
it("can receive a PreKeySignalMessage", it("can receive a PreKeySignalMessage",
mock.initConverseWithPromises( mock.initConverseWithPromises(
null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
function (done, _converse) { async function (done, _converse) {
_converse.NUM_PREKEYS = 5; // Restrict to 5, otherwise the resulting stanza is too large to easily test _converse.NUM_PREKEYS = 5; // Restrict to 5, otherwise the resulting stanza is too large to easily test
let view, sent_stanza; let view, sent_stanza;
...@@ -242,717 +220,667 @@ ...@@ -242,717 +220,667 @@
_converse.emit('rosterContactsFetched'); _converse.emit('rosterContactsFetched');
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
return test_utils.waitUntil(() => initializedOMEMO(_converse)) await test_utils.waitUntil(() => initializedOMEMO(_converse));
.then(() => _converse.ChatBox.prototype.encryptMessage('This is an encrypted message from the contact')) const obj = await _converse.ChatBox.prototype.encryptMessage('This is an encrypted message from the contact');
.then(obj => { // XXX: Normally the key will be encrypted via libsignal.
// XXX: Normally the key will be encrypted via libsignal. // However, we're mocking libsignal in the tests, so we include
// However, we're mocking libsignal in the tests, so we include // it as plaintext in the message.
// it as plaintext in the message. let stanza = $msg({
const stanza = $msg({
'from': contact_jid,
'to': _converse.connection.jid,
'type': 'chat',
'id': 'qwerty'
}).c('body').t('This is a fallback message').up()
.c('encrypted', {'xmlns': Strophe.NS.OMEMO})
.c('header', {'sid': '555'})
.c('key', {
'prekey': 'true',
'rid': _converse.omemo_store.get('device_id')
}).t(u.arrayBufferToBase64(obj.key_and_tag)).up()
.c('iv').t(obj.iv)
.up().up()
.c('payload').t(obj.payload);
const generateMissingPreKeys = _converse.omemo_store.generateMissingPreKeys;
spyOn(_converse.omemo_store, 'generateMissingPreKeys').and.callFake(() => {
// Since it's difficult to override
// decryptPreKeyWhisperMessage, where a prekey will be
// removed from the store, we do it here, before the
// missing prekeys are generated.
_converse.omemo_store.removePreKey(1);
return generateMissingPreKeys.apply(_converse.omemo_store, arguments);
});
_converse.connection._dataRecv(test_utils.createRequest(stanza));
return test_utils.waitUntil(() => _converse.chatboxviews.get(contact_jid))
}).then(iq_stanza => deviceListFetched(_converse, contact_jid))
.then(iq_stanza => {
const stanza = $iq({
'from': contact_jid, 'from': contact_jid,
'id': iq_stanza.nodeTree.getAttribute('id'),
'to': _converse.connection.jid, 'to': _converse.connection.jid,
'type': 'result', 'type': 'chat',
}).c('pubsub', {'xmlns': "http://jabber.org/protocol/pubsub"}) 'id': 'qwerty'
.c('items', {'node': "eu.siacs.conversations.axolotl.devicelist"}) }).c('body').t('This is a fallback message').up()
.c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute .c('encrypted', {'xmlns': Strophe.NS.OMEMO})
.c('list', {'xmlns': "eu.siacs.conversations.axolotl"}) .c('header', {'sid': '555'})
.c('device', {'id': '555'}); .c('key', {
'prekey': 'true',
'rid': _converse.omemo_store.get('device_id')
}).t(u.arrayBufferToBase64(obj.key_and_tag)).up()
.c('iv').t(obj.iv)
.up().up()
.c('payload').t(obj.payload);
const generateMissingPreKeys = _converse.omemo_store.generateMissingPreKeys;
spyOn(_converse.omemo_store, 'generateMissingPreKeys').and.callFake(() => {
// Since it's difficult to override
// decryptPreKeyWhisperMessage, where a prekey will be
// removed from the store, we do it here, before the
// missing prekeys are generated.
_converse.omemo_store.removePreKey(1);
return generateMissingPreKeys.apply(_converse.omemo_store, arguments);
});
_converse.connection._dataRecv(test_utils.createRequest(stanza));
let iq_stanza = await test_utils.waitUntil(() => _converse.chatboxviews.get(contact_jid));
iq_stanza = await deviceListFetched(_converse, contact_jid);
stanza = $iq({
'from': contact_jid,
'id': iq_stanza.nodeTree.getAttribute('id'),
'to': _converse.connection.jid,
'type': 'result',
}).c('pubsub', {'xmlns': "http://jabber.org/protocol/pubsub"})
.c('items', {'node': "eu.siacs.conversations.axolotl.devicelist"})
.c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute
.c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
.c('device', {'id': '555'});
// XXX: the bundle gets published twice, we want to make sure // XXX: the bundle gets published twice, we want to make sure
// that we wait for the 2nd, so we clear all the already sent // that we wait for the 2nd, so we clear all the already sent
// stanzas. // stanzas.
_converse.connection.IQ_stanzas = []; _converse.connection.IQ_stanzas = [];
_converse.connection._dataRecv(test_utils.createRequest(stanza)); _converse.connection._dataRecv(test_utils.createRequest(stanza));
return test_utils.waitUntil(() => _converse.omemo_store); await test_utils.waitUntil(() => _converse.omemo_store);
}).then(() => test_utils.waitUntil(() => bundleHasBeenPublished(_converse))) iq_stanza = await test_utils.waitUntil(() => bundleHasBeenPublished(_converse));
.then(iq_stanza => { expect(iq_stanza.toLocaleString()).toBe(
expect(iq_stanza.toLocaleString()).toBe( `<iq from="dummy@localhost" id="${iq_stanza.nodeTree.getAttribute("id")}" type="set" xmlns="jabber:client">`+
`<iq from="dummy@localhost" id="${iq_stanza.nodeTree.getAttribute("id")}" type="set" xmlns="jabber:client">`+ `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
`<pubsub xmlns="http://jabber.org/protocol/pubsub">`+ `<publish node="eu.siacs.conversations.axolotl.bundles:123456789">`+
`<publish node="eu.siacs.conversations.axolotl.bundles:123456789">`+ `<item>`+
`<item>`+ `<bundle xmlns="eu.siacs.conversations.axolotl">`+
`<bundle xmlns="eu.siacs.conversations.axolotl">`+ `<signedPreKeyPublic signedPreKeyId="0">${btoa("1234")}</signedPreKeyPublic>`+
`<signedPreKeyPublic signedPreKeyId="0">${btoa("1234")}</signedPreKeyPublic>`+ `<signedPreKeySignature>${btoa("11112222333344445555")}</signedPreKeySignature>`+
`<signedPreKeySignature>${btoa("11112222333344445555")}</signedPreKeySignature>`+ `<identityKey>${btoa("1234")}</identityKey>`+
`<identityKey>${btoa("1234")}</identityKey>`+ `<prekeys>`+
`<prekeys>`+ `<preKeyPublic preKeyId="0">${btoa("1234")}</preKeyPublic>`+
`<preKeyPublic preKeyId="0">${btoa("1234")}</preKeyPublic>`+ `<preKeyPublic preKeyId="1">${btoa("1234")}</preKeyPublic>`+
`<preKeyPublic preKeyId="1">${btoa("1234")}</preKeyPublic>`+ `<preKeyPublic preKeyId="2">${btoa("1234")}</preKeyPublic>`+
`<preKeyPublic preKeyId="2">${btoa("1234")}</preKeyPublic>`+ `<preKeyPublic preKeyId="3">${btoa("1234")}</preKeyPublic>`+
`<preKeyPublic preKeyId="3">${btoa("1234")}</preKeyPublic>`+ `<preKeyPublic preKeyId="4">${btoa("1234")}</preKeyPublic>`+
`<preKeyPublic preKeyId="4">${btoa("1234")}</preKeyPublic>`+ `</prekeys>`+
`</prekeys>`+ `</bundle>`+
`</bundle>`+ `</item>`+
`</item>`+ `</publish>`+
`</publish>`+ `</pubsub>`+
`</pubsub>`+ `</iq>`)
`</iq>`) const own_device = _converse.devicelists.get(_converse.bare_jid).devices.get(_converse.omemo_store.get('device_id'));
const own_device = _converse.devicelists.get(_converse.bare_jid).devices.get(_converse.omemo_store.get('device_id')); expect(own_device.get('bundle').prekeys.length).toBe(5);
expect(own_device.get('bundle').prekeys.length).toBe(5); expect(_converse.omemo_store.generateMissingPreKeys).toHaveBeenCalled();
expect(_converse.omemo_store.generateMissingPreKeys).toHaveBeenCalled(); done();
done();
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL))
})); }));
it("updates device lists based on PEP messages", it("updates device lists based on PEP messages",
mock.initConverseWithPromises( mock.initConverseWithPromises(
null, ['rosterGroupsFetched'], {'allow_non_roster_messaging': true}, null, ['rosterGroupsFetched'], {'allow_non_roster_messaging': true},
function (done, _converse) { async function (done, _converse) {
test_utils.createContacts(_converse, 'current', 1); test_utils.createContacts(_converse, 'current', 1);
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
// Wait until own devices are fetched // Wait until own devices are fetched
test_utils.waitUntil(() => deviceListFetched(_converse, _converse.bare_jid)) let iq_stanza = await test_utils.waitUntil(() => deviceListFetched(_converse, _converse.bare_jid));
.then(iq_stanza => { expect(iq_stanza.toLocaleString()).toBe(
expect(iq_stanza.toLocaleString()).toBe( `<iq from="dummy@localhost" id="${iq_stanza.nodeTree.getAttribute("id")}" to="dummy@localhost" type="get" xmlns="jabber:client">`+
`<iq from="dummy@localhost" id="${iq_stanza.nodeTree.getAttribute("id")}" to="dummy@localhost" type="get" xmlns="jabber:client">`+ `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
`<pubsub xmlns="http://jabber.org/protocol/pubsub">`+ `<items node="eu.siacs.conversations.axolotl.devicelist"/>`+
`<items node="eu.siacs.conversations.axolotl.devicelist"/>`+ `</pubsub>`+
`</pubsub>`+ `</iq>`);
`</iq>`);
let stanza = $iq({
const stanza = $iq({ 'from': _converse.bare_jid,
'from': _converse.bare_jid, 'id': iq_stanza.nodeTree.getAttribute('id'),
'id': iq_stanza.nodeTree.getAttribute('id'), 'to': _converse.bare_jid,
'to': _converse.bare_jid, 'type': 'result',
'type': 'result', }).c('pubsub', {'xmlns': "http://jabber.org/protocol/pubsub"})
}).c('pubsub', {'xmlns': "http://jabber.org/protocol/pubsub"}) .c('items', {'node': "eu.siacs.conversations.axolotl.devicelist"})
.c('items', {'node': "eu.siacs.conversations.axolotl.devicelist"}) .c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute
.c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute .c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
.c('list', {'xmlns': "eu.siacs.conversations.axolotl"}) .c('device', {'id': '555'});
.c('device', {'id': '555'}); _converse.connection._dataRecv(test_utils.createRequest(stanza));
_converse.connection._dataRecv(test_utils.createRequest(stanza)); await test_utils.waitUntil(() => _converse.omemo_store);
return test_utils.waitUntil(() => _converse.omemo_store); expect(_converse.chatboxes.length).toBe(1);
}).then(() => { expect(_converse.devicelists.length).toBe(1);
expect(_converse.chatboxes.length).toBe(1); const devicelist = _converse.devicelists.get(_converse.bare_jid);
expect(_converse.devicelists.length).toBe(1); expect(devicelist.devices.length).toBe(2);
const devicelist = _converse.devicelists.get(_converse.bare_jid); expect(devicelist.devices.at(0).get('id')).toBe('555');
expect(devicelist.devices.length).toBe(2); expect(devicelist.devices.at(1).get('id')).toBe('123456789');
expect(devicelist.devices.at(0).get('id')).toBe('555'); iq_stanza = await test_utils.waitUntil(() => ownDeviceHasBeenPublished(_converse));
expect(devicelist.devices.at(1).get('id')).toBe('123456789'); stanza = $iq({
return test_utils.waitUntil(() => ownDeviceHasBeenPublished(_converse)); 'from': _converse.bare_jid,
}).then(iq_stanza => { 'id': iq_stanza.nodeTree.getAttribute('id'),
const stanza = $iq({ 'to': _converse.bare_jid,
'from': _converse.bare_jid, 'type': 'result'});
'id': iq_stanza.nodeTree.getAttribute('id'), _converse.connection._dataRecv(test_utils.createRequest(stanza));
'to': _converse.bare_jid, iq_stanza = await test_utils.waitUntil(() => bundleHasBeenPublished(_converse));
'type': 'result'});
_converse.connection._dataRecv(test_utils.createRequest(stanza)); stanza = $iq({
return test_utils.waitUntil(() => bundleHasBeenPublished(_converse)); 'from': _converse.bare_jid,
}).then(iq_stanza => { 'id': iq_stanza.nodeTree.getAttribute('id'),
const stanza = $iq({ 'to': _converse.bare_jid,
'from': _converse.bare_jid, 'type': 'result'});
'id': iq_stanza.nodeTree.getAttribute('id'), _converse.connection._dataRecv(test_utils.createRequest(stanza));
'to': _converse.bare_jid, await _converse.api.waitUntil('OMEMOInitialized');
'type': 'result'});
_converse.connection._dataRecv(test_utils.createRequest(stanza)); stanza = $msg({
return _converse.api.waitUntil('OMEMOInitialized'); 'from': contact_jid,
}).then(() => { 'to': _converse.bare_jid,
let stanza = $msg({ 'type': 'headline',
'from': contact_jid, 'id': 'update_01',
'to': _converse.bare_jid, }).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'})
'type': 'headline', .c('items', {'node': 'eu.siacs.conversations.axolotl.devicelist'})
'id': 'update_01', .c('item')
}).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'}) .c('list', {'xmlns': 'eu.siacs.conversations.axolotl'})
.c('items', {'node': 'eu.siacs.conversations.axolotl.devicelist'}) .c('device', {'id': '1234'})
.c('item') .c('device', {'id': '4223'})
.c('list', {'xmlns': 'eu.siacs.conversations.axolotl'}) _converse.connection._dataRecv(test_utils.createRequest(stanza));
.c('device', {'id': '1234'})
.c('device', {'id': '4223'}) expect(_converse.devicelists.length).toBe(2);
_converse.connection._dataRecv(test_utils.createRequest(stanza)); let devices = _converse.devicelists.get(contact_jid).devices;
expect(devices.length).toBe(2);
expect(_converse.devicelists.length).toBe(2); expect(_.map(devices.models, 'attributes.id').sort().join()).toBe('1234,4223');
let devices = _converse.devicelists.get(contact_jid).devices;
expect(devices.length).toBe(2); stanza = $msg({
expect(_.map(devices.models, 'attributes.id').sort().join()).toBe('1234,4223'); 'from': contact_jid,
'to': _converse.bare_jid,
stanza = $msg({ 'type': 'headline',
'from': contact_jid, 'id': 'update_02',
'to': _converse.bare_jid, }).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'})
'type': 'headline', .c('items', {'node': 'eu.siacs.conversations.axolotl.devicelist'})
'id': 'update_02', .c('item')
}).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'}) .c('list', {'xmlns': 'eu.siacs.conversations.axolotl'})
.c('items', {'node': 'eu.siacs.conversations.axolotl.devicelist'}) .c('device', {'id': '4223'})
.c('item') .c('device', {'id': '4224'})
.c('list', {'xmlns': 'eu.siacs.conversations.axolotl'}) _converse.connection._dataRecv(test_utils.createRequest(stanza));
.c('device', {'id': '4223'})
.c('device', {'id': '4224'}) expect(_converse.devicelists.length).toBe(2);
_converse.connection._dataRecv(test_utils.createRequest(stanza)); expect(devices.length).toBe(2);
expect(_.map(devices.models, 'attributes.id').sort().join()).toBe('4223,4224');
expect(_converse.devicelists.length).toBe(2);
expect(devices.length).toBe(2); // Check that own devicelist gets updated
expect(_.map(devices.models, 'attributes.id').sort().join()).toBe('4223,4224'); stanza = $msg({
'from': _converse.bare_jid,
// Check that own devicelist gets updated 'to': _converse.bare_jid,
stanza = $msg({ 'type': 'headline',
'from': _converse.bare_jid, 'id': 'update_03',
'to': _converse.bare_jid, }).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'})
'type': 'headline', .c('items', {'node': 'eu.siacs.conversations.axolotl.devicelist'})
'id': 'update_03', .c('item')
}).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'}) .c('list', {'xmlns': 'eu.siacs.conversations.axolotl'})
.c('items', {'node': 'eu.siacs.conversations.axolotl.devicelist'}) .c('device', {'id': '123456789'})
.c('item') .c('device', {'id': '555'})
.c('list', {'xmlns': 'eu.siacs.conversations.axolotl'}) .c('device', {'id': '777'})
.c('device', {'id': '123456789'}) _converse.connection._dataRecv(test_utils.createRequest(stanza));
.c('device', {'id': '555'})
.c('device', {'id': '777'}) expect(_converse.devicelists.length).toBe(2);
_converse.connection._dataRecv(test_utils.createRequest(stanza)); devices = _converse.devicelists.get(_converse.bare_jid).devices;
expect(devices.length).toBe(3);
expect(_converse.devicelists.length).toBe(2); expect(_.map(devices.models, 'attributes.id').sort().join()).toBe('123456789,555,777');
devices = _converse.devicelists.get(_converse.bare_jid).devices;
expect(devices.length).toBe(3); _converse.connection.IQ_stanzas = [];
expect(_.map(devices.models, 'attributes.id').sort().join()).toBe('123456789,555,777');
// Check that own device gets re-added
_converse.connection.IQ_stanzas = []; stanza = $msg({
'from': _converse.bare_jid,
// Check that own device gets re-added 'to': _converse.bare_jid,
stanza = $msg({ 'type': 'headline',
'from': _converse.bare_jid, 'id': 'update_04',
'to': _converse.bare_jid, }).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'})
'type': 'headline', .c('items', {'node': 'eu.siacs.conversations.axolotl.devicelist'})
'id': 'update_04', .c('item')
}).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'}) .c('list', {'xmlns': 'eu.siacs.conversations.axolotl'})
.c('items', {'node': 'eu.siacs.conversations.axolotl.devicelist'}) .c('device', {'id': '444'})
.c('item') _converse.connection._dataRecv(test_utils.createRequest(stanza));
.c('list', {'xmlns': 'eu.siacs.conversations.axolotl'})
.c('device', {'id': '444'}) iq_stanza = await test_utils.waitUntil(() => ownDeviceHasBeenPublished(_converse));
_converse.connection._dataRecv(test_utils.createRequest(stanza)); // Check that our own device is added again, but that removed
// devices are not added.
return test_utils.waitUntil(() => ownDeviceHasBeenPublished(_converse)); expect(iq_stanza.toLocaleString()).toBe(
}).then(iq_stanza => { `<iq from="dummy@localhost" id="${iq_stanza.nodeTree.getAttribute(`id`)}" type="set" xmlns="jabber:client">`+
// Check that our own device is added again, but that removed `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
// devices are not added. `<publish node="eu.siacs.conversations.axolotl.devicelist">`+
expect(iq_stanza.toLocaleString()).toBe( `<item>`+
`<iq from="dummy@localhost" id="${iq_stanza.nodeTree.getAttribute(`id`)}" type="set" xmlns="jabber:client">`+ `<list xmlns="eu.siacs.conversations.axolotl">`+
`<pubsub xmlns="http://jabber.org/protocol/pubsub">`+ `<device id="123456789"/>`+
`<publish node="eu.siacs.conversations.axolotl.devicelist">`+ `<device id="444"/>`+
`<item>`+ `</list>`+
`<list xmlns="eu.siacs.conversations.axolotl">`+ `</item>`+
`<device id="123456789"/>`+ `</publish>`+
`<device id="444"/>`+ `</pubsub>`+
`</list>`+ `</iq>`);
`</item>`+ expect(_converse.devicelists.length).toBe(2);
`</publish>`+ devices = _converse.devicelists.get(_converse.bare_jid).devices;
`</pubsub>`+ // The device id for this device (123456789) was also generated and added to the list,
`</iq>`); // which is why we have 2 devices now.
expect(_converse.devicelists.length).toBe(2); expect(devices.length).toBe(2);
const devices = _converse.devicelists.get(_converse.bare_jid).devices; expect(_.map(devices.models, 'attributes.id').sort().join()).toBe('123456789,444');
// The device id for this device (123456789) was also generated and added to the list, done();
// which is why we have 2 devices now.
expect(devices.length).toBe(2);
expect(_.map(devices.models, 'attributes.id').sort().join()).toBe('123456789,444');
done();
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL))
})); }));
it("updates device bundles based on PEP messages", it("updates device bundles based on PEP messages",
mock.initConverseWithPromises( mock.initConverseWithPromises(
null, ['rosterGroupsFetched'], {}, null, ['rosterGroupsFetched'], {},
function (done, _converse) { async function (done, _converse) {
test_utils.createContacts(_converse, 'current'); test_utils.createContacts(_converse, 'current');
const contact_jid = mock.cur_names[3].replace(/ /g,'.').toLowerCase() + '@localhost'; const contact_jid = mock.cur_names[3].replace(/ /g,'.').toLowerCase() + '@localhost';
let iq_stanza = await test_utils.waitUntil(() => deviceListFetched(_converse, _converse.bare_jid));
expect(iq_stanza.toLocaleString()).toBe(
`<iq from="dummy@localhost" id="${iq_stanza.nodeTree.getAttribute("id")}" to="dummy@localhost" type="get" xmlns="jabber:client">`+
`<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
`<items node="eu.siacs.conversations.axolotl.devicelist"/>`+
`</pubsub>`+
`</iq>`);
let stanza = $iq({
'from': contact_jid,
'id': iq_stanza.nodeTree.getAttribute('id'),
'to': _converse.bare_jid,
'type': 'result',
}).c('pubsub', {'xmlns': "http://jabber.org/protocol/pubsub"})
.c('items', {'node': "eu.siacs.conversations.axolotl.devicelist"})
.c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute
.c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
.c('device', {'id': '555'});
_converse.connection._dataRecv(test_utils.createRequest(stanza));
await await test_utils.waitUntil(() => _converse.omemo_store);
expect(_converse.devicelists.length).toBe(1);
let devicelist = _converse.devicelists.get(_converse.bare_jid);
expect(devicelist.devices.length).toBe(2);
expect(devicelist.devices.at(0).get('id')).toBe('555');
expect(devicelist.devices.at(1).get('id')).toBe('123456789');
iq_stanza = await test_utils.waitUntil(() => ownDeviceHasBeenPublished(_converse));
stanza = $iq({
'from': _converse.bare_jid,
'id': iq_stanza.nodeTree.getAttribute('id'),
'to': _converse.bare_jid,
'type': 'result'});
_converse.connection._dataRecv(test_utils.createRequest(stanza));
iq_stanza = await test_utils.waitUntil(() => bundleHasBeenPublished(_converse));
stanza = $iq({
'from': _converse.bare_jid,
'id': iq_stanza.nodeTree.getAttribute('id'),
'to': _converse.bare_jid,
'type': 'result'});
_converse.connection._dataRecv(test_utils.createRequest(stanza));
await _converse.api.waitUntil('OMEMOInitialized');
stanza = $msg({
'from': contact_jid,
'to': _converse.bare_jid,
'type': 'headline',
'id': 'update_01',
}).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'})
.c('items', {'node': 'eu.siacs.conversations.axolotl.bundles:555'})
.c('item')
.c('bundle', {'xmlns': 'eu.siacs.conversations.axolotl'})
.c('signedPreKeyPublic', {'signedPreKeyId': '4223'}).t('1111').up()
.c('signedPreKeySignature').t('2222').up()
.c('identityKey').t('3333').up()
.c('prekeys')
.c('preKeyPublic', {'preKeyId': '1001'}).up()
.c('preKeyPublic', {'preKeyId': '1002'}).up()
.c('preKeyPublic', {'preKeyId': '1003'});
_converse.connection._dataRecv(test_utils.createRequest(stanza));
test_utils.waitUntil(() => deviceListFetched(_converse, _converse.bare_jid)) expect(_converse.devicelists.length).toBe(2);
.then(iq_stanza => { devicelist = _converse.devicelists.get(contact_jid);
expect(iq_stanza.toLocaleString()).toBe( expect(devicelist.devices.length).toBe(1);
`<iq from="dummy@localhost" id="${iq_stanza.nodeTree.getAttribute("id")}" to="dummy@localhost" type="get" xmlns="jabber:client">`+ let device = devicelist.devices.at(0);
`<pubsub xmlns="http://jabber.org/protocol/pubsub">`+ expect(device.get('bundle').identity_key).toBe('3333');
`<items node="eu.siacs.conversations.axolotl.devicelist"/>`+ expect(device.get('bundle').signed_prekey.public_key).toBe('1111');
`</pubsub>`+ expect(device.get('bundle').signed_prekey.id).toBe(4223);
`</iq>`); expect(device.get('bundle').signed_prekey.signature).toBe('2222');
expect(device.get('bundle').prekeys.length).toBe(3);
expect(device.get('bundle').prekeys[0].id).toBe(1001);
expect(device.get('bundle').prekeys[1].id).toBe(1002);
expect(device.get('bundle').prekeys[2].id).toBe(1003);
stanza = $msg({
'from': contact_jid,
'to': _converse.bare_jid,
'type': 'headline',
'id': 'update_02',
}).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'})
.c('items', {'node': 'eu.siacs.conversations.axolotl.bundles:555'})
.c('item')
.c('bundle', {'xmlns': 'eu.siacs.conversations.axolotl'})
.c('signedPreKeyPublic', {'signedPreKeyId': '4223'}).t('5555').up()
.c('signedPreKeySignature').t('6666').up()
.c('identityKey').t('7777').up()
.c('prekeys')
.c('preKeyPublic', {'preKeyId': '2001'}).up()
.c('preKeyPublic', {'preKeyId': '2002'}).up()
.c('preKeyPublic', {'preKeyId': '2003'});
_converse.connection._dataRecv(test_utils.createRequest(stanza));
const stanza = $iq({ expect(_converse.devicelists.length).toBe(2);
'from': contact_jid, devicelist = _converse.devicelists.get(contact_jid);
'id': iq_stanza.nodeTree.getAttribute('id'), expect(devicelist.devices.length).toBe(1);
'to': _converse.bare_jid, device = devicelist.devices.at(0);
'type': 'result', expect(device.get('bundle').identity_key).toBe('7777');
}).c('pubsub', {'xmlns': "http://jabber.org/protocol/pubsub"}) expect(device.get('bundle').signed_prekey.public_key).toBe('5555');
.c('items', {'node': "eu.siacs.conversations.axolotl.devicelist"}) expect(device.get('bundle').signed_prekey.id).toBe(4223);
.c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute expect(device.get('bundle').signed_prekey.signature).toBe('6666');
.c('list', {'xmlns': "eu.siacs.conversations.axolotl"}) expect(device.get('bundle').prekeys.length).toBe(3);
.c('device', {'id': '555'}); expect(device.get('bundle').prekeys[0].id).toBe(2001);
_converse.connection._dataRecv(test_utils.createRequest(stanza)); expect(device.get('bundle').prekeys[1].id).toBe(2002);
return test_utils.waitUntil(() => _converse.omemo_store); expect(device.get('bundle').prekeys[2].id).toBe(2003);
}).then(() => {
expect(_converse.devicelists.length).toBe(1); stanza = $msg({
const devicelist = _converse.devicelists.get(_converse.bare_jid); 'from': _converse.bare_jid,
expect(devicelist.devices.length).toBe(2); 'to': _converse.bare_jid,
expect(devicelist.devices.at(0).get('id')).toBe('555'); 'type': 'headline',
expect(devicelist.devices.at(1).get('id')).toBe('123456789'); 'id': 'update_03',
return test_utils.waitUntil(() => ownDeviceHasBeenPublished(_converse)); }).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'})
}).then(iq_stanza => { .c('items', {'node': 'eu.siacs.conversations.axolotl.bundles:123456789'})
const stanza = $iq({ .c('item')
'from': _converse.bare_jid, .c('bundle', {'xmlns': 'eu.siacs.conversations.axolotl'})
'id': iq_stanza.nodeTree.getAttribute('id'), .c('signedPreKeyPublic', {'signedPreKeyId': '9999'}).t('8888').up()
'to': _converse.bare_jid, .c('signedPreKeySignature').t('3333').up()
'type': 'result'}); .c('identityKey').t('1111').up()
_converse.connection._dataRecv(test_utils.createRequest(stanza)); .c('prekeys')
return test_utils.waitUntil(() => bundleHasBeenPublished(_converse)); .c('preKeyPublic', {'preKeyId': '3001'}).up()
}).then(iq_stanza => { .c('preKeyPublic', {'preKeyId': '3002'}).up()
const stanza = $iq({ .c('preKeyPublic', {'preKeyId': '3003'});
'from': _converse.bare_jid, _converse.connection._dataRecv(test_utils.createRequest(stanza));
'id': iq_stanza.nodeTree.getAttribute('id'),
'to': _converse.bare_jid, expect(_converse.devicelists.length).toBe(2);
'type': 'result'}); devicelist = _converse.devicelists.get(_converse.bare_jid);
_converse.connection._dataRecv(test_utils.createRequest(stanza)); expect(devicelist.devices.length).toBe(2);
return _converse.api.waitUntil('OMEMOInitialized'); expect(devicelist.devices.at(0).get('id')).toBe('555');
}).then(() => { expect(devicelist.devices.at(1).get('id')).toBe('123456789');
let stanza = $msg({ device = devicelist.devices.at(1);
'from': contact_jid, expect(device.get('bundle').identity_key).toBe('1111');
'to': _converse.bare_jid, expect(device.get('bundle').signed_prekey.public_key).toBe('8888');
'type': 'headline', expect(device.get('bundle').signed_prekey.id).toBe(9999);
'id': 'update_01', expect(device.get('bundle').signed_prekey.signature).toBe('3333');
}).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'}) expect(device.get('bundle').prekeys.length).toBe(3);
.c('items', {'node': 'eu.siacs.conversations.axolotl.bundles:555'}) expect(device.get('bundle').prekeys[0].id).toBe(3001);
.c('item') expect(device.get('bundle').prekeys[1].id).toBe(3002);
.c('bundle', {'xmlns': 'eu.siacs.conversations.axolotl'}) expect(device.get('bundle').prekeys[2].id).toBe(3003);
.c('signedPreKeyPublic', {'signedPreKeyId': '4223'}).t('1111').up() done();
.c('signedPreKeySignature').t('2222').up()
.c('identityKey').t('3333').up()
.c('prekeys')
.c('preKeyPublic', {'preKeyId': '1001'}).up()
.c('preKeyPublic', {'preKeyId': '1002'}).up()
.c('preKeyPublic', {'preKeyId': '1003'});
_converse.connection._dataRecv(test_utils.createRequest(stanza));
expect(_converse.devicelists.length).toBe(2);
let devicelist = _converse.devicelists.get(contact_jid);
expect(devicelist.devices.length).toBe(1);
let device = devicelist.devices.at(0);
expect(device.get('bundle').identity_key).toBe('3333');
expect(device.get('bundle').signed_prekey.public_key).toBe('1111');
expect(device.get('bundle').signed_prekey.id).toBe(4223);
expect(device.get('bundle').signed_prekey.signature).toBe('2222');
expect(device.get('bundle').prekeys.length).toBe(3);
expect(device.get('bundle').prekeys[0].id).toBe(1001);
expect(device.get('bundle').prekeys[1].id).toBe(1002);
expect(device.get('bundle').prekeys[2].id).toBe(1003);
stanza = $msg({
'from': contact_jid,
'to': _converse.bare_jid,
'type': 'headline',
'id': 'update_02',
}).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'})
.c('items', {'node': 'eu.siacs.conversations.axolotl.bundles:555'})
.c('item')
.c('bundle', {'xmlns': 'eu.siacs.conversations.axolotl'})
.c('signedPreKeyPublic', {'signedPreKeyId': '4223'}).t('5555').up()
.c('signedPreKeySignature').t('6666').up()
.c('identityKey').t('7777').up()
.c('prekeys')
.c('preKeyPublic', {'preKeyId': '2001'}).up()
.c('preKeyPublic', {'preKeyId': '2002'}).up()
.c('preKeyPublic', {'preKeyId': '2003'});
_converse.connection._dataRecv(test_utils.createRequest(stanza));
expect(_converse.devicelists.length).toBe(2);
devicelist = _converse.devicelists.get(contact_jid);
expect(devicelist.devices.length).toBe(1);
device = devicelist.devices.at(0);
expect(device.get('bundle').identity_key).toBe('7777');
expect(device.get('bundle').signed_prekey.public_key).toBe('5555');
expect(device.get('bundle').signed_prekey.id).toBe(4223);
expect(device.get('bundle').signed_prekey.signature).toBe('6666');
expect(device.get('bundle').prekeys.length).toBe(3);
expect(device.get('bundle').prekeys[0].id).toBe(2001);
expect(device.get('bundle').prekeys[1].id).toBe(2002);
expect(device.get('bundle').prekeys[2].id).toBe(2003);
stanza = $msg({
'from': _converse.bare_jid,
'to': _converse.bare_jid,
'type': 'headline',
'id': 'update_03',
}).c('event', {'xmlns': 'http://jabber.org/protocol/pubsub#event'})
.c('items', {'node': 'eu.siacs.conversations.axolotl.bundles:123456789'})
.c('item')
.c('bundle', {'xmlns': 'eu.siacs.conversations.axolotl'})
.c('signedPreKeyPublic', {'signedPreKeyId': '9999'}).t('8888').up()
.c('signedPreKeySignature').t('3333').up()
.c('identityKey').t('1111').up()
.c('prekeys')
.c('preKeyPublic', {'preKeyId': '3001'}).up()
.c('preKeyPublic', {'preKeyId': '3002'}).up()
.c('preKeyPublic', {'preKeyId': '3003'});
_converse.connection._dataRecv(test_utils.createRequest(stanza));
expect(_converse.devicelists.length).toBe(2);
devicelist = _converse.devicelists.get(_converse.bare_jid);
expect(devicelist.devices.length).toBe(2);
expect(devicelist.devices.at(0).get('id')).toBe('555');
expect(devicelist.devices.at(1).get('id')).toBe('123456789');
device = devicelist.devices.at(1);
expect(device.get('bundle').identity_key).toBe('1111');
expect(device.get('bundle').signed_prekey.public_key).toBe('8888');
expect(device.get('bundle').signed_prekey.id).toBe(9999);
expect(device.get('bundle').signed_prekey.signature).toBe('3333');
expect(device.get('bundle').prekeys.length).toBe(3);
expect(device.get('bundle').prekeys[0].id).toBe(3001);
expect(device.get('bundle').prekeys[1].id).toBe(3002);
expect(device.get('bundle').prekeys[2].id).toBe(3003);
done();
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL))
})); }));
it("publishes a bundle with which an encrypted session can be created", it("publishes a bundle with which an encrypted session can be created",
mock.initConverseWithPromises( mock.initConverseWithPromises(
null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
function (done, _converse) { async function (done, _converse) {
_converse.NUM_PREKEYS = 2; // Restrict to 2, otherwise the resulting stanza is too large to easily test _converse.NUM_PREKEYS = 2; // Restrict to 2, otherwise the resulting stanza is too large to easily test
test_utils.createContacts(_converse, 'current', 1); test_utils.createContacts(_converse, 'current', 1);
_converse.emit('rosterContactsFetched'); _converse.emit('rosterContactsFetched');
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
let iq_stanza = await test_utils.waitUntil(() => deviceListFetched(_converse, _converse.bare_jid));
let stanza = $iq({
'from': contact_jid,
'id': iq_stanza.nodeTree.getAttribute('id'),
'to': _converse.bare_jid,
'type': 'result',
}).c('pubsub', {'xmlns': "http://jabber.org/protocol/pubsub"})
.c('items', {'node': "eu.siacs.conversations.axolotl.devicelist"})
.c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute
.c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
.c('device', {'id': '482886413b977930064a5888b92134fe'});
_converse.connection._dataRecv(test_utils.createRequest(stanza));
expect(_converse.devicelists.length).toBe(1);
await test_utils.openChatBoxFor(_converse, contact_jid);
iq_stanza = await ownDeviceHasBeenPublished(_converse);
stanza = $iq({
'from': _converse.bare_jid,
'id': iq_stanza.nodeTree.getAttribute('id'),
'to': _converse.bare_jid,
'type': 'result'});
_converse.connection._dataRecv(test_utils.createRequest(stanza));
test_utils.waitUntil(() => deviceListFetched(_converse, _converse.bare_jid)) iq_stanza = await test_utils.waitUntil(() => bundleHasBeenPublished(_converse));
.then(iq_stanza => { expect(iq_stanza.toLocaleString()).toBe(
const stanza = $iq({ `<iq from="dummy@localhost" id="${iq_stanza.nodeTree.getAttribute("id")}" type="set" xmlns="jabber:client">`+
'from': contact_jid, `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
'id': iq_stanza.nodeTree.getAttribute('id'), `<publish node="eu.siacs.conversations.axolotl.bundles:123456789">`+
'to': _converse.bare_jid, `<item>`+
'type': 'result', `<bundle xmlns="eu.siacs.conversations.axolotl">`+
}).c('pubsub', {'xmlns': "http://jabber.org/protocol/pubsub"}) `<signedPreKeyPublic signedPreKeyId="0">${btoa("1234")}</signedPreKeyPublic>`+
.c('items', {'node': "eu.siacs.conversations.axolotl.devicelist"}) `<signedPreKeySignature>${btoa("11112222333344445555")}</signedPreKeySignature>`+
.c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute `<identityKey>${btoa("1234")}</identityKey>`+
.c('list', {'xmlns': "eu.siacs.conversations.axolotl"}) `<prekeys>`+
.c('device', {'id': '482886413b977930064a5888b92134fe'}); `<preKeyPublic preKeyId="0">${btoa("1234")}</preKeyPublic>`+
_converse.connection._dataRecv(test_utils.createRequest(stanza)); `<preKeyPublic preKeyId="1">${btoa("1234")}</preKeyPublic>`+
expect(_converse.devicelists.length).toBe(1); `</prekeys>`+
return test_utils.openChatBoxFor(_converse, contact_jid); `</bundle>`+
`</item>`+
}).then(() => ownDeviceHasBeenPublished(_converse)) `</publish>`+
.then(iq_stanza => { `</pubsub>`+
const stanza = $iq({ `</iq>`)
'from': _converse.bare_jid,
'id': iq_stanza.nodeTree.getAttribute('id'), stanza = $iq({
'to': _converse.bare_jid, 'from': _converse.bare_jid,
'type': 'result'}); 'id': iq_stanza.nodeTree.getAttribute('id'),
_converse.connection._dataRecv(test_utils.createRequest(stanza)); 'to': _converse.bare_jid,
'type': 'result'});
return test_utils.waitUntil(() => bundleHasBeenPublished(_converse)); _converse.connection._dataRecv(test_utils.createRequest(stanza));
}).then(iq_stanza => { await _converse.api.waitUntil('OMEMOInitialized');
expect(iq_stanza.toLocaleString()).toBe( done();
`<iq from="dummy@localhost" id="${iq_stanza.nodeTree.getAttribute("id")}" type="set" xmlns="jabber:client">`+
`<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
`<publish node="eu.siacs.conversations.axolotl.bundles:123456789">`+
`<item>`+
`<bundle xmlns="eu.siacs.conversations.axolotl">`+
`<signedPreKeyPublic signedPreKeyId="0">${btoa("1234")}</signedPreKeyPublic>`+
`<signedPreKeySignature>${btoa("11112222333344445555")}</signedPreKeySignature>`+
`<identityKey>${btoa("1234")}</identityKey>`+
`<prekeys>`+
`<preKeyPublic preKeyId="0">${btoa("1234")}</preKeyPublic>`+
`<preKeyPublic preKeyId="1">${btoa("1234")}</preKeyPublic>`+
`</prekeys>`+
`</bundle>`+
`</item>`+
`</publish>`+
`</pubsub>`+
`</iq>`)
const stanza = $iq({
'from': _converse.bare_jid,
'id': iq_stanza.nodeTree.getAttribute('id'),
'to': _converse.bare_jid,
'type': 'result'});
_converse.connection._dataRecv(test_utils.createRequest(stanza));
return _converse.api.waitUntil('OMEMOInitialized');
}).then(done).catch(_.partial(console.error, _));
})); }));
it("adds a toolbar button for starting an encrypted chat session", it("adds a toolbar button for starting an encrypted chat session",
mock.initConverseWithPromises( mock.initConverseWithPromises(
null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
function (done, _converse) { async function (done, _converse) {
let modal;
test_utils.createContacts(_converse, 'current', 1); test_utils.createContacts(_converse, 'current', 1);
_converse.emit('rosterContactsFetched'); _converse.emit('rosterContactsFetched');
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
test_utils.waitUntil(() => deviceListFetched(_converse, _converse.bare_jid)) let iq_stanza = await test_utils.waitUntil(() => deviceListFetched(_converse, _converse.bare_jid));
.then(iq_stanza => { expect(iq_stanza.toLocaleString()).toBe(
expect(iq_stanza.toLocaleString()).toBe( `<iq from="dummy@localhost" id="${iq_stanza.nodeTree.getAttribute("id")}" to="dummy@localhost" type="get" xmlns="jabber:client">`+
`<iq from="dummy@localhost" id="${iq_stanza.nodeTree.getAttribute("id")}" to="dummy@localhost" type="get" xmlns="jabber:client">`+ `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
`<pubsub xmlns="http://jabber.org/protocol/pubsub">`+ `<items node="eu.siacs.conversations.axolotl.devicelist"/>`+
`<items node="eu.siacs.conversations.axolotl.devicelist"/>`+
`</pubsub>`+
`</iq>`);
const stanza = $iq({
'from': _converse.bare_jid,
'id': iq_stanza.nodeTree.getAttribute('id'),
'to': _converse.bare_jid,
'type': 'result',
}).c('pubsub', {'xmlns': "http://jabber.org/protocol/pubsub"})
.c('items', {'node': "eu.siacs.conversations.axolotl.devicelist"})
.c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute
.c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
.c('device', {'id': '482886413b977930064a5888b92134fe'});
_converse.connection._dataRecv(test_utils.createRequest(stanza));
return test_utils.waitUntil(() => _converse.omemo_store);
}).then(() => {
expect(_converse.devicelists.length).toBe(1);
const devicelist = _converse.devicelists.get(_converse.bare_jid);
expect(devicelist.devices.length).toBe(2);
expect(devicelist.devices.at(0).get('id')).toBe('482886413b977930064a5888b92134fe');
expect(devicelist.devices.at(1).get('id')).toBe('123456789');
// Check that own device was published
return test_utils.waitUntil(() => ownDeviceHasBeenPublished(_converse));
}).then(iq_stanza => {
expect(iq_stanza.toLocaleString()).toBe(
`<iq from="dummy@localhost" id="${iq_stanza.nodeTree.getAttribute(`id`)}" type="set" xmlns="jabber:client">`+
`<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
`<publish node="eu.siacs.conversations.axolotl.devicelist">`+
`<item>`+
`<list xmlns="eu.siacs.conversations.axolotl">`+
`<device id="482886413b977930064a5888b92134fe"/>`+
`<device id="123456789"/>`+
`</list>`+
`</item>`+
`</publish>`+
`</pubsub>`+ `</pubsub>`+
`</iq>`); `</iq>`);
const stanza = $iq({ let stanza = $iq({
'from': _converse.bare_jid, 'from': _converse.bare_jid,
'id': iq_stanza.nodeTree.getAttribute('id'), 'id': iq_stanza.nodeTree.getAttribute('id'),
'to': _converse.bare_jid, 'to': _converse.bare_jid,
'type': 'result'}); 'type': 'result',
_converse.connection._dataRecv(test_utils.createRequest(stanza)); }).c('pubsub', {'xmlns': "http://jabber.org/protocol/pubsub"})
.c('items', {'node': "eu.siacs.conversations.axolotl.devicelist"})
return test_utils.waitUntil(() => _.get(bundleHasBeenPublished(_converse), 'nodeTree')); .c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute
}).then(iq_el => { .c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
expect(iq_el.getAttributeNames().sort().join()).toBe(["from", "type", "xmlns", "id"].sort().join()); .c('device', {'id': '482886413b977930064a5888b92134fe'});
expect(iq_el.querySelector('prekeys').childNodes.length).toBe(100); _converse.connection._dataRecv(test_utils.createRequest(stanza));
await test_utils.waitUntil(() => _converse.omemo_store);
const signed_prekeys = iq_el.querySelectorAll('signedPreKeyPublic'); expect(_converse.devicelists.length).toBe(1);
expect(signed_prekeys.length).toBe(1); let devicelist = _converse.devicelists.get(_converse.bare_jid);
const signed_prekey = signed_prekeys[0]; expect(devicelist.devices.length).toBe(2);
expect(signed_prekey.getAttribute('signedPreKeyId')).toBe('0') expect(devicelist.devices.at(0).get('id')).toBe('482886413b977930064a5888b92134fe');
expect(iq_el.querySelectorAll('signedPreKeySignature').length).toBe(1); expect(devicelist.devices.at(1).get('id')).toBe('123456789');
expect(iq_el.querySelectorAll('identityKey').length).toBe(1); // Check that own device was published
iq_stanza = await test_utils.waitUntil(() => ownDeviceHasBeenPublished(_converse));
const stanza = $iq({ expect(iq_stanza.toLocaleString()).toBe(
'from': _converse.bare_jid, `<iq from="dummy@localhost" id="${iq_stanza.nodeTree.getAttribute(`id`)}" type="set" xmlns="jabber:client">`+
'id': iq_el.getAttribute('id'), `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
'to': _converse.bare_jid, `<publish node="eu.siacs.conversations.axolotl.devicelist">`+
'type': 'result'}); `<item>`+
_converse.connection._dataRecv(test_utils.createRequest(stanza)); `<list xmlns="eu.siacs.conversations.axolotl">`+
return _converse.api.waitUntil('OMEMOInitialized', 1000); `<device id="482886413b977930064a5888b92134fe"/>`+
}).then(() => { `<device id="123456789"/>`+
return test_utils.openChatBoxFor(_converse, contact_jid); `</list>`+
}).then(() => { `</item>`+
return test_utils.waitUntil(() => deviceListFetched(_converse, contact_jid)); `</publish>`+
}).then(iq_stanza => { `</pubsub>`+
expect(iq_stanza.toLocaleString()).toBe( `</iq>`);
`<iq from="dummy@localhost" id="${iq_stanza.nodeTree.getAttribute("id")}" to="${contact_jid}" type="get" xmlns="jabber:client">`+
`<pubsub xmlns="http://jabber.org/protocol/pubsub">`+ stanza = $iq({
`<items node="eu.siacs.conversations.axolotl.devicelist"/>`+ 'from': _converse.bare_jid,
`</pubsub>`+ 'id': iq_stanza.nodeTree.getAttribute('id'),
`</iq>`); 'to': _converse.bare_jid,
'type': 'result'});
const stanza = $iq({ _converse.connection._dataRecv(test_utils.createRequest(stanza));
'from': contact_jid,
'id': iq_stanza.nodeTree.getAttribute('id'), const iq_el = await test_utils.waitUntil(() => _.get(bundleHasBeenPublished(_converse), 'nodeTree'));
'to': _converse.bare_jid, expect(iq_el.getAttributeNames().sort().join()).toBe(["from", "type", "xmlns", "id"].sort().join());
'type': 'result', expect(iq_el.querySelector('prekeys').childNodes.length).toBe(100);
}).c('pubsub', {'xmlns': "http://jabber.org/protocol/pubsub"})
.c('items', {'node': "eu.siacs.conversations.axolotl.devicelist"}) const signed_prekeys = iq_el.querySelectorAll('signedPreKeyPublic');
.c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute expect(signed_prekeys.length).toBe(1);
.c('list', {'xmlns': "eu.siacs.conversations.axolotl"}) const signed_prekey = signed_prekeys[0];
.c('device', {'id': '368866411b877c30064a5f62b917cffe'}).up() expect(signed_prekey.getAttribute('signedPreKeyId')).toBe('0')
.c('device', {'id': '3300659945416e274474e469a1f0154c'}).up() expect(iq_el.querySelectorAll('signedPreKeySignature').length).toBe(1);
.c('device', {'id': '4e30f35051b7b8b42abe083742187228'}).up() expect(iq_el.querySelectorAll('identityKey').length).toBe(1);
.c('device', {'id': 'ae890ac52d0df67ed7cfdf51b644e901'});
_converse.connection._dataRecv(test_utils.createRequest(stanza)); stanza = $iq({
const devicelist = _converse.devicelists.get(contact_jid); 'from': _converse.bare_jid,
return test_utils.waitUntil(() => devicelist.devices.length); 'id': iq_el.getAttribute('id'),
}).then(() => { 'to': _converse.bare_jid,
expect(_converse.devicelists.length).toBe(2); 'type': 'result'});
const devicelist = _converse.devicelists.get(contact_jid); _converse.connection._dataRecv(test_utils.createRequest(stanza));
expect(devicelist.devices.length).toBe(4); await _converse.api.waitUntil('OMEMOInitialized', 1000);
expect(devicelist.devices.at(0).get('id')).toBe('368866411b877c30064a5f62b917cffe'); await test_utils.openChatBoxFor(_converse, contact_jid);
expect(devicelist.devices.at(1).get('id')).toBe('3300659945416e274474e469a1f0154c'); iq_stanza = await test_utils.waitUntil(() => deviceListFetched(_converse, contact_jid));
expect(devicelist.devices.at(2).get('id')).toBe('4e30f35051b7b8b42abe083742187228'); expect(iq_stanza.toLocaleString()).toBe(
expect(devicelist.devices.at(3).get('id')).toBe('ae890ac52d0df67ed7cfdf51b644e901'); `<iq from="dummy@localhost" id="${iq_stanza.nodeTree.getAttribute("id")}" to="${contact_jid}" type="get" xmlns="jabber:client">`+
return test_utils.waitUntil(() => _converse.chatboxviews.get(contact_jid).el.querySelector('.chat-toolbar')); `<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
}).then(() => { `<items node="eu.siacs.conversations.axolotl.devicelist"/>`+
const view = _converse.chatboxviews.get(contact_jid); `</pubsub>`+
const toolbar = view.el.querySelector('.chat-toolbar'); `</iq>`);
expect(view.model.get('omemo_active')).toBe(undefined);
const toggle = toolbar.querySelector('.toggle-omemo'); stanza = $iq({
expect(_.isNull(toggle)).toBe(false); 'from': contact_jid,
expect(u.hasClass('fa-unlock', toggle)).toBe(true); 'id': iq_stanza.nodeTree.getAttribute('id'),
expect(u.hasClass('fa-lock', toggle)).toBe(false); 'to': _converse.bare_jid,
'type': 'result',
spyOn(view, 'toggleOMEMO').and.callThrough(); }).c('pubsub', {'xmlns': "http://jabber.org/protocol/pubsub"})
view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called .c('items', {'node': "eu.siacs.conversations.axolotl.devicelist"})
toolbar.querySelector('.toggle-omemo').click(); .c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute
expect(view.toggleOMEMO).toHaveBeenCalled(); .c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
expect(view.model.get('omemo_active')).toBe(true); .c('device', {'id': '368866411b877c30064a5f62b917cffe'}).up()
.c('device', {'id': '3300659945416e274474e469a1f0154c'}).up()
return test_utils.waitUntil(() => u.hasClass('fa-lock', toolbar.querySelector('.toggle-omemo'))); .c('device', {'id': '4e30f35051b7b8b42abe083742187228'}).up()
}).then(() => { .c('device', {'id': 'ae890ac52d0df67ed7cfdf51b644e901'});
const view = _converse.chatboxviews.get(contact_jid); _converse.connection._dataRecv(test_utils.createRequest(stanza));
const toolbar = view.el.querySelector('.chat-toolbar'); devicelist = _converse.devicelists.get(contact_jid);
const toggle = toolbar.querySelector('.toggle-omemo'); await test_utils.waitUntil(() => devicelist.devices.length);
expect(u.hasClass('fa-unlock', toggle)).toBe(false); expect(_converse.devicelists.length).toBe(2);
expect(u.hasClass('fa-lock', toggle)).toBe(true); devicelist = _converse.devicelists.get(contact_jid);
expect(devicelist.devices.length).toBe(4);
const textarea = view.el.querySelector('.chat-textarea'); expect(devicelist.devices.at(0).get('id')).toBe('368866411b877c30064a5f62b917cffe');
textarea.value = 'This message will be sent encrypted'; expect(devicelist.devices.at(1).get('id')).toBe('3300659945416e274474e469a1f0154c');
view.keyPressed({ expect(devicelist.devices.at(2).get('id')).toBe('4e30f35051b7b8b42abe083742187228');
target: textarea, expect(devicelist.devices.at(3).get('id')).toBe('ae890ac52d0df67ed7cfdf51b644e901');
preventDefault: _.noop, await test_utils.waitUntil(() => _converse.chatboxviews.get(contact_jid).el.querySelector('.chat-toolbar'));
keyCode: 13 const view = _converse.chatboxviews.get(contact_jid);
}); const toolbar = view.el.querySelector('.chat-toolbar');
done(); expect(view.model.get('omemo_active')).toBe(undefined);
}).catch(_.partial(console.error, _)); let toggle = toolbar.querySelector('.toggle-omemo');
expect(_.isNull(toggle)).toBe(false);
expect(u.hasClass('fa-unlock', toggle)).toBe(true);
expect(u.hasClass('fa-lock', toggle)).toBe(false);
spyOn(view, 'toggleOMEMO').and.callThrough();
view.delegateEvents(); // We need to rebind all events otherwise our spy won't be called
toolbar.querySelector('.toggle-omemo').click();
expect(view.toggleOMEMO).toHaveBeenCalled();
expect(view.model.get('omemo_active')).toBe(true);
await test_utils.waitUntil(() => u.hasClass('fa-lock', toolbar.querySelector('.toggle-omemo')));
toggle = toolbar.querySelector('.toggle-omemo');
expect(u.hasClass('fa-unlock', toggle)).toBe(false);
expect(u.hasClass('fa-lock', toggle)).toBe(true);
const textarea = view.el.querySelector('.chat-textarea');
textarea.value = 'This message will be sent encrypted';
view.keyPressed({
target: textarea,
preventDefault: _.noop,
keyCode: 13
});
done();
})); }));
it("shows OMEMO device fingerprints in the user details modal", it("shows OMEMO device fingerprints in the user details modal",
mock.initConverseWithPromises( mock.initConverseWithPromises(
null, ['rosterGroupsFetched', 'chatBoxesFetched'], {}, null, ['rosterGroupsFetched', 'chatBoxesFetched'], {},
function (done, _converse) { async function (done, _converse) {
let modal;
test_utils.createContacts(_converse, 'current', 1); test_utils.createContacts(_converse, 'current', 1);
_converse.emit('rosterContactsFetched'); _converse.emit('rosterContactsFetched');
const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost'; const contact_jid = mock.cur_names[0].replace(/ /g,'.').toLowerCase() + '@localhost';
test_utils.openChatBoxFor(_converse, contact_jid) await test_utils.openChatBoxFor(_converse, contact_jid)
.then(() => { // We simply emit, to avoid doing all the setup work
// We simply emit, to avoid doing all the setup work _converse.emit('OMEMOInitialized');
_converse.emit('OMEMOInitialized');
const view = _converse.chatboxviews.get(contact_jid);
const view = _converse.chatboxviews.get(contact_jid); const show_modal_button = view.el.querySelector('.show-user-details-modal');
const show_modal_button = view.el.querySelector('.show-user-details-modal'); show_modal_button.click();
show_modal_button.click(); const modal = view.user_details_modal;
modal = view.user_details_modal; await test_utils.waitUntil(() => u.isVisible(modal.el), 1000);
let iq_stanza = await test_utils.waitUntil(() => deviceListFetched(_converse, contact_jid));
return test_utils.waitUntil(() => u.isVisible(modal.el), 1000); expect(iq_stanza.toLocaleString()).toBe(
}).then(() => test_utils.waitUntil(() => deviceListFetched(_converse, contact_jid))) `<iq from="dummy@localhost" id="${iq_stanza.nodeTree.getAttribute("id")}" to="max.frankfurter@localhost" type="get" xmlns="jabber:client">`+
.then(iq_stanza => { `<pubsub xmlns="http://jabber.org/protocol/pubsub"><items node="eu.siacs.conversations.axolotl.devicelist"/></pubsub>`+
expect(iq_stanza.toLocaleString()).toBe( `</iq>`);
`<iq from="dummy@localhost" id="${iq_stanza.nodeTree.getAttribute("id")}" to="max.frankfurter@localhost" type="get" xmlns="jabber:client">`+ let stanza = $iq({
`<pubsub xmlns="http://jabber.org/protocol/pubsub"><items node="eu.siacs.conversations.axolotl.devicelist"/></pubsub>`+ 'from': contact_jid,
`</iq>`); 'id': iq_stanza.nodeTree.getAttribute('id'),
'to': _converse.bare_jid,
const stanza = $iq({ 'type': 'result',
'from': contact_jid, }).c('pubsub', {'xmlns': "http://jabber.org/protocol/pubsub"})
'id': iq_stanza.nodeTree.getAttribute('id'), .c('items', {'node': "eu.siacs.conversations.axolotl.devicelist"})
'to': _converse.bare_jid, .c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute
'type': 'result', .c('list', {'xmlns': "eu.siacs.conversations.axolotl"})
}).c('pubsub', {'xmlns': "http://jabber.org/protocol/pubsub"}) .c('device', {'id': '555'});
.c('items', {'node': "eu.siacs.conversations.axolotl.devicelist"}) _converse.connection._dataRecv(test_utils.createRequest(stanza));
.c('item', {'xmlns': "http://jabber.org/protocol/pubsub"}) // TODO: must have an id attribute await test_utils.waitUntil(() => u.isVisible(modal.el), 1000);
.c('list', {'xmlns': "eu.siacs.conversations.axolotl"}) iq_stanza = await test_utils.waitUntil(() => bundleFetched(_converse, contact_jid, '555'));
.c('device', {'id': '555'}); expect(iq_stanza.toLocaleString()).toBe(
_converse.connection._dataRecv(test_utils.createRequest(stanza)); `<iq from="dummy@localhost" id="${iq_stanza.nodeTree.getAttribute("id")}" to="max.frankfurter@localhost" type="get" xmlns="jabber:client">`+
`<pubsub xmlns="http://jabber.org/protocol/pubsub">`+
return test_utils.waitUntil(() => u.isVisible(modal.el), 1000); `<items node="eu.siacs.conversations.axolotl.bundles:555"/>`+
}).then(() => test_utils.waitUntil(() => bundleFetched(_converse, contact_jid, '555'))) `</pubsub>`+
.then(iq_stanza => { `</iq>`);
expect(iq_stanza.toLocaleString()).toBe( stanza = $iq({
`<iq from="dummy@localhost" id="${iq_stanza.nodeTree.getAttribute("id")}" to="max.frankfurter@localhost" type="get" xmlns="jabber:client">`+ 'from': contact_jid,
`<pubsub xmlns="http://jabber.org/protocol/pubsub">`+ 'id': iq_stanza.nodeTree.getAttribute('id'),
`<items node="eu.siacs.conversations.axolotl.bundles:555"/>`+ 'to': _converse.bare_jid,
`</pubsub>`+ 'type': 'result',
`</iq>`); }).c('pubsub', {
'xmlns': 'http://jabber.org/protocol/pubsub'
const stanza = $iq({ }).c('items', {'node': "eu.siacs.conversations.axolotl.bundles:555"})
'from': contact_jid, .c('item')
'id': iq_stanza.nodeTree.getAttribute('id'), .c('bundle', {'xmlns': 'eu.siacs.conversations.axolotl'})
'to': _converse.bare_jid, .c('signedPreKeyPublic', {'signedPreKeyId': '4223'}).t(btoa('1111')).up()
'type': 'result', .c('signedPreKeySignature').t(btoa('2222')).up()
}).c('pubsub', { .c('identityKey').t('BQmHEOHjsYm3w5M8VqxAtqJmLCi7CaxxsdZz6G0YpuMI').up()
'xmlns': 'http://jabber.org/protocol/pubsub' .c('prekeys')
}).c('items', {'node': "eu.siacs.conversations.axolotl.bundles:555"}) .c('preKeyPublic', {'preKeyId': '1'}).t(btoa('1001')).up()
.c('item') .c('preKeyPublic', {'preKeyId': '2'}).t(btoa('1002')).up()
.c('bundle', {'xmlns': 'eu.siacs.conversations.axolotl'}) .c('preKeyPublic', {'preKeyId': '3'}).t(btoa('1003'));
.c('signedPreKeyPublic', {'signedPreKeyId': '4223'}).t(btoa('1111')).up() _converse.connection._dataRecv(test_utils.createRequest(stanza));
.c('signedPreKeySignature').t(btoa('2222')).up()
.c('identityKey').t('BQmHEOHjsYm3w5M8VqxAtqJmLCi7CaxxsdZz6G0YpuMI').up() await test_utils.waitUntil(() => modal.el.querySelectorAll('.fingerprints .fingerprint').length);
.c('prekeys') expect(modal.el.querySelectorAll('.fingerprints .fingerprint').length).toBe(1);
.c('preKeyPublic', {'preKeyId': '1'}).t(btoa('1001')).up() const el = modal.el.querySelector('.fingerprints .fingerprint');
.c('preKeyPublic', {'preKeyId': '2'}).t(btoa('1002')).up() expect(el.textContent.trim()).toBe(
.c('preKeyPublic', {'preKeyId': '3'}).t(btoa('1003')); u.formatFingerprint(u.arrayBufferToHex(u.base64ToArrayBuffer('BQmHEOHjsYm3w5M8VqxAtqJmLCi7CaxxsdZz6G0YpuMI')))
_converse.connection._dataRecv(test_utils.createRequest(stanza)); );
expect(modal.el.querySelectorAll('input[type="radio"]').length).toBe(2);
const view = _converse.chatboxviews.get(contact_jid);
const modal = view.user_details_modal; const devicelist = _converse.devicelists.get(contact_jid);
return test_utils.waitUntil(() => modal.el.querySelectorAll('.fingerprints .fingerprint').length); expect(devicelist.devices.get('555').get('trusted')).toBe(0);
}).then(() => {
const view = _converse.chatboxviews.get(contact_jid); let trusted_radio = modal.el.querySelector('input[type="radio"][name="555"][value="1"]');
const modal = view.user_details_modal; expect(trusted_radio.checked).toBe(true);
expect(modal.el.querySelectorAll('.fingerprints .fingerprint').length).toBe(1);
const el = modal.el.querySelector('.fingerprints .fingerprint'); let untrusted_radio = modal.el.querySelector('input[type="radio"][name="555"][value="-1"]');
expect(el.textContent.trim()).toBe( expect(untrusted_radio.checked).toBe(false);
u.formatFingerprint(u.arrayBufferToHex(u.base64ToArrayBuffer('BQmHEOHjsYm3w5M8VqxAtqJmLCi7CaxxsdZz6G0YpuMI')))
); // Test that the device can be set to untrusted
untrusted_radio.click();
expect(modal.el.querySelectorAll('input[type="radio"]').length).toBe(2); trusted_radio = document.querySelector('input[type="radio"][name="555"][value="1"]');
expect(trusted_radio.hasAttribute('checked')).toBe(false);
const devicelist = _converse.devicelists.get(contact_jid); expect(devicelist.devices.get('555').get('trusted')).toBe(-1);
expect(devicelist.devices.get('555').get('trusted')).toBe(0);
untrusted_radio = document.querySelector('input[type="radio"][name="555"][value="-1"]');
let trusted_radio = modal.el.querySelector('input[type="radio"][name="555"][value="1"]'); expect(untrusted_radio.hasAttribute('checked')).toBe(true);
expect(trusted_radio.checked).toBe(true);
trusted_radio.click();
let untrusted_radio = modal.el.querySelector('input[type="radio"][name="555"][value="-1"]'); expect(devicelist.devices.get('555').get('trusted')).toBe(1);
expect(untrusted_radio.checked).toBe(false); done();
// Test that the device can be set to untrusted
untrusted_radio.click();
trusted_radio = document.querySelector('input[type="radio"][name="555"][value="1"]');
expect(trusted_radio.hasAttribute('checked')).toBe(false);
expect(devicelist.devices.get('555').get('trusted')).toBe(-1);
untrusted_radio = document.querySelector('input[type="radio"][name="555"][value="-1"]');
expect(untrusted_radio.hasAttribute('checked')).toBe(true);
trusted_radio.click();
expect(devicelist.devices.get('555').get('trusted')).toBe(1);
done();
}).catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL))
})); }));
}); });
......
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