diff --git a/src/headless/plugins/smacks/tests/smacks.js b/src/headless/plugins/smacks/tests/smacks.js index b73210f0d..1373b3b08 100644 --- a/src/headless/plugins/smacks/tests/smacks.js +++ b/src/headless/plugins/smacks/tests/smacks.js @@ -30,7 +30,7 @@ describe("XEP-0198 Stream Management", function () { expect(_converse.session.get('smacks_enabled')).toBe(true); let IQ_stanzas = _converse.connection.IQ_stanzas; - await u.waitUntil(() => IQ_stanzas.length === 4); + await u.waitUntil(() => IQ_stanzas.length === 5); let iq = IQ_stanzas[IQ_stanzas.length-1]; expect(Strophe.serialize(iq)).toBe( @@ -57,13 +57,13 @@ describe("XEP-0198 Stream Management", function () { iq = IQ_stanzas.pop(); expect(expected_IQs(disco_iq).includes(Strophe.serialize(disco_iq))).toBe(true); - expect(sent_stanzas.filter(s => (s.nodeName === 'r')).length).toBe(2); - expect(_converse.session.get('unacked_stanzas').length).toBe(5); + expect(sent_stanzas.filter(s => (s.nodeName === 'r')).length).toBe(3); + expect(_converse.session.get('unacked_stanzas').length).toBe(6); // test handling of acks let ack = u.toStanza(``); _converse.connection._dataRecv(mock.createRequest(ack)); - expect(_converse.session.get('unacked_stanzas').length).toBe(3); + expect(_converse.session.get('unacked_stanzas').length).toBe(4); // test handling of ack requests let r = u.toStanza(``); @@ -89,7 +89,7 @@ describe("XEP-0198 Stream Management", function () { ack = u.toStanza(``); _converse.connection._dataRecv(mock.createRequest(ack)); - expect(_converse.session.get('unacked_stanzas').length).toBe(2); + expect(_converse.session.get('unacked_stanzas').length).toBe(3); r = u.toStanza(``); _converse.connection._dataRecv(mock.createRequest(r)); @@ -112,12 +112,17 @@ describe("XEP-0198 Stream Management", function () { expect(_converse.session.get('smacks_enabled')).toBe(true); await new Promise(resolve => _converse.api.listen.once('reconnected', resolve)); - await u.waitUntil(() => IQ_stanzas.length === 1); + await u.waitUntil(() => IQ_stanzas.length === 2); // Test that unacked stanzas get resent out iq = IQ_stanzas.pop(); expect(Strophe.serialize(iq)).toBe(``); + iq = IQ_stanzas.pop(); + expect(Strophe.serialize(iq)).toBe( + ``+ + ``); + expect(IQ_stanzas.filter(iq => sizzle('query[xmlns="jabber:iq:roster"]', iq).pop()).length).toBe(0); })); diff --git a/src/plugins/omemo/api.js b/src/plugins/omemo/api.js index 0fc426417..888173ea7 100644 --- a/src/plugins/omemo/api.js +++ b/src/plugins/omemo/api.js @@ -18,6 +18,29 @@ export default { return _converse.omemo_store.get('device_id'); }, + /** + * The "devicelists" namespace groups methods related to OMEMO device lists + * + * @namespace _converse.api.omemo.devicelists + * @memberOf _converse.api.omemo + */ + 'devicelists': { + /** + * Returns the {@link _converse.DeviceList} for a particular JID. + * The device list will be created if it doesn't exist already. + * @method _converse.api.omemo.devicelists.get + * @param { String } jid - The Jabber ID for which the device list will be returned. + * @param { bool } create=false - Set to `true` if the device list + * should be created if it cannot be found. + */ + async get (jid, create=false) { + const list = _converse.devicelists.get(jid) || + (create ? _converse.devicelists.create({ jid }) : null); + await list.initialized; + return list; + } + }, + /** * The "bundle" namespace groups methods relevant to the user's * OMEMO bundle. @@ -35,7 +58,8 @@ export default { 'generate': async () => { await api.waitUntil('OMEMOInitialized'); // Remove current device - const devicelist = _converse.devicelists.get(_converse.bare_jid); + const devicelist = await api.omemo.devicelists.get(_converse.bare_jid); + const device_id = _converse.omemo_store.get('device_id'); if (device_id) { const device = devicelist.devices.get(device_id); diff --git a/src/plugins/omemo/devicelist.js b/src/plugins/omemo/devicelist.js index d571e1ab1..63529fd2e 100644 --- a/src/plugins/omemo/devicelist.js +++ b/src/plugins/omemo/devicelist.js @@ -43,7 +43,7 @@ const DeviceList = Model.extend({ this.destroy(); } if (this.get('jid') === _converse.bare_jid) { - await this.publishCurrentDevice(ids); + this.publishCurrentDevice(ids); } } }, @@ -95,23 +95,14 @@ const DeviceList = Model.extend({ 'type': 'get', 'from': _converse.bare_jid, 'to': this.get('jid') - }) - .c('pubsub', { 'xmlns': Strophe.NS.PUBSUB }) - .c('items', { 'node': Strophe.NS.OMEMO_DEVICELIST }); + }).c('pubsub', { 'xmlns': Strophe.NS.PUBSUB }) + .c('items', { 'node': Strophe.NS.OMEMO_DEVICELIST }); - let iq; - try { - iq = await api.sendIQ(stanza); - } catch (e) { - log.error(e); - return []; - } + const iq = await api.sendIQ(stanza); const selector = `list[xmlns="${Strophe.NS.OMEMO}"] device`; const device_ids = sizzle(selector, iq).map(d => d.getAttribute('id')); - await Promise.all( - device_ids.map(id => this.devices.create({ id, 'jid': this.get('jid') }, { 'promise': true })) - ); - return device_ids; + const jid = this.get('jid'); + return Promise.all(device_ids.map(id => this.devices.create({ id, jid }, { 'promise': true }))); }, /** diff --git a/src/plugins/omemo/devicelists.js b/src/plugins/omemo/devicelists.js index f516394cd..a42462a76 100644 --- a/src/plugins/omemo/devicelists.js +++ b/src/plugins/omemo/devicelists.js @@ -6,20 +6,6 @@ import { Collection } from '@converse/skeletor/src/collection'; * @namespace _converse.DeviceLists * @memberOf _converse */ -const DeviceLists = Collection.extend({ - model: DeviceList, - - /** - * Returns the {@link _converse.DeviceList} for a particular JID. - * The device list will be created if it doesn't exist already. - * @method _converse.DeviceLists#getDeviceList - * @param { String } jid - The Jabber ID for which the device list will be returned. - */ - async getDeviceList (jid) { - const list = this.get(jid) || this.create({ 'jid': jid }); - await list.initialized; - return list; - } -}); +const DeviceLists = Collection.extend({ model: DeviceList }); export default DeviceLists; diff --git a/src/plugins/omemo/fingerprints.js b/src/plugins/omemo/fingerprints.js index fdf0bd122..411feca22 100644 --- a/src/plugins/omemo/fingerprints.js +++ b/src/plugins/omemo/fingerprints.js @@ -1,6 +1,6 @@ import tpl_fingerprints from './templates/fingerprints.js'; import { CustomElement } from 'shared/components/element.js'; -import { _converse, api } from "@converse/headless/core"; +import { api } from "@converse/headless/core"; export class Fingerprints extends CustomElement { @@ -11,7 +11,7 @@ export class Fingerprints extends CustomElement { } async initialize () { - this.devicelist = await _converse.devicelists.getDeviceList(this.jid); + this.devicelist = await api.omemo.devicelists.get(this.jid, true); this.listenTo(this.devicelist.devices, 'change:bundle', this.requestUpdate); this.listenTo(this.devicelist.devices, 'change:trusted', this.requestUpdate); this.listenTo(this.devicelist.devices, 'remove', this.requestUpdate); diff --git a/src/plugins/omemo/profile.js b/src/plugins/omemo/profile.js index 1fbcbc913..93b826946 100644 --- a/src/plugins/omemo/profile.js +++ b/src/plugins/omemo/profile.js @@ -10,7 +10,7 @@ const { Strophe, sizzle, u } = converse.env; export class Profile extends CustomElement { async initialize () { - this.devicelist = await _converse.devicelists.getDeviceList(_converse.bare_jid); + this.devicelist = await api.omemo.devicelists.get(_converse.bare_jid, true); await this.setAttributes(); this.listenTo(this.devicelist.devices, 'change:bundle', () => this.requestUpdate()); this.listenTo(this.devicelist.devices, 'reset', () => this.requestUpdate()); diff --git a/src/plugins/omemo/store.js b/src/plugins/omemo/store.js index 05c18a80a..f2a5b3d66 100644 --- a/src/plugins/omemo/store.js +++ b/src/plugins/omemo/store.js @@ -199,7 +199,7 @@ const OMEMOStore = Model.extend({ 'id': k.keyId, 'key': u.arrayBufferToBase64(k.pubKey) })); - const devicelist = _converse.devicelists.get(_converse.bare_jid); + const devicelist = await api.omemo.devicelists.get(_converse.bare_jid); const device = devicelist.devices.get(this.get('device_id')); const bundle = await device.getBundle(); device.save('bundle', Object.assign(bundle, { 'prekeys': marshalled_keys })); @@ -218,7 +218,7 @@ const OMEMOStore = Model.extend({ const identity_keypair = await libsignal.KeyHelper.generateIdentityKeyPair(); const bundle = {}; const identity_key = u.arrayBufferToBase64(identity_keypair.pubKey); - const device_id = generateDeviceID(); + const device_id = await generateDeviceID(); bundle['identity_key'] = identity_key; bundle['device_id'] = device_id; @@ -242,7 +242,7 @@ const OMEMOStore = Model.extend({ range(0, _converse.NUM_PREKEYS).map(id => libsignal.KeyHelper.generatePreKey(id)) ); keys.forEach(k => this.storePreKey(k.keyId, k.keyPair)); - const devicelist = _converse.devicelists.get(_converse.bare_jid); + const devicelist = await api.omemo.devicelists.get(_converse.bare_jid); const device = await devicelist.devices.create( { 'id': bundle.device_id, 'jid': _converse.bare_jid }, { 'promise': true } diff --git a/src/plugins/omemo/utils.js b/src/plugins/omemo/utils.js index ad462e592..fd792477e 100644 --- a/src/plugins/omemo/utils.js +++ b/src/plugins/omemo/utils.js @@ -302,7 +302,7 @@ function getJIDForDecryption (attrs) { async function handleDecryptedWhisperMessage (attrs, key_and_tag) { const from_jid = getJIDForDecryption(attrs); - const devicelist = await _converse.devicelists.getDeviceList(from_jid); + const devicelist = await api.omemo.devicelists.get(from_jid, true); const encrypted = attrs.encrypted; let device = devicelist.devices.get(encrypted.device_id); if (!device) { @@ -446,14 +446,15 @@ export async function generateFingerprint (device) { export async function getDevicesForContact (jid) { await api.waitUntil('OMEMOInitialized'); - const devicelist = _converse.devicelists.get(jid) || _converse.devicelists.create({ 'jid': jid }); + const devicelist = await api.omemo.devicelists.get(jid, true); await devicelist.fetchDevices(); return devicelist.devices; } -export function generateDeviceID () { +export async function generateDeviceID () { /* Generates a device ID, making sure that it's unique */ - const existing_ids = _converse.devicelists.get(_converse.bare_jid).devices.pluck('id'); + const devicelist = await api.omemo.devicelists.get(_converse.bare_jid); + const existing_ids = devicelist.devices.pluck('id'); let device_id = libsignal.KeyHelper.generateRegistrationId(); // Before publishing a freshly generated device id for the first time, @@ -519,7 +520,7 @@ async function updateBundleFromStanza (stanza) { const device_id = items_el.getAttribute('node').split(':')[1]; const jid = stanza.getAttribute('from'); const bundle_el = sizzle(`item > bundle`, items_el).pop(); - const devicelist = await _converse.devicelists.getDeviceList(jid); + const devicelist = await api.omemo.devicelists.get(jid, true); const device = devicelist.devices.get(device_id) || devicelist.devices.create({ 'id': device_id, jid }); device.save({ 'bundle': parseBundle(bundle_el) }); } @@ -532,7 +533,7 @@ async function updateDevicesFromStanza (stanza) { const device_selector = `item list[xmlns="${Strophe.NS.OMEMO}"] device`; const device_ids = sizzle(device_selector, items_el).map(d => d.getAttribute('id')); const jid = stanza.getAttribute('from'); - const devicelist = await _converse.devicelists.getDeviceList(jid); + const devicelist = await api.omemo.devicelists.get(jid, true); const devices = devicelist.devices; const removed_ids = difference(devices.pluck('id'), device_ids); @@ -578,35 +579,29 @@ export function registerPEPPushHandler () { ); } -export function restoreOMEMOSession () { +export async function restoreOMEMOSession () { if (_converse.omemo_store === undefined) { const id = `converse.omemosession-${_converse.bare_jid}`; _converse.omemo_store = new _converse.OMEMOStore({ id }); initStorage(_converse.omemo_store, id); } - return _converse.omemo_store.fetchSession(); + await _converse.omemo_store.fetchSession(); } async function fetchDeviceLists () { - _converse.devicelists = new _converse.DeviceLists(); const id = `converse.devicelists-${_converse.bare_jid}`; + _converse.devicelists = new _converse.DeviceLists({ id }); initStorage(_converse.devicelists, id); await new Promise(resolve => { _converse.devicelists.fetch({ 'success': resolve, - 'error': (m, e) => { - log.error(e); - resolve(); - } + 'error': (m, e) => { log.error(e); resolve(); } }) }); - const promises = _converse.devicelists.map(l => l.initialized); - if (!_converse.devicelists.get(_converse.bare_jid)) { - // Create own device list if we none was restored - const own_list = await _converse.devicelists.create({ 'jid': _converse.bare_jid }, { 'promise': true }); - return Promise.all([...promises, own_list.initialized]); - } - return Promise.all(promises); + // Call API method to wait for our own device list to be fetched from the + // server or to be created. If we have no pre-existing OMEMO session, this + // will cause a new device and bundle to be generated and published. + await api.omemo.devicelists.get(_converse.bare_jid, true); } export async function initOMEMO (reconnecting) { @@ -748,7 +743,8 @@ export async function getBundlesAndBuildSessions (chatbox) { err.user_facing = true; throw err; } - const own_devices = _converse.devicelists.get(_converse.bare_jid).devices; + const own_list = await api.omemo.devicelists.get(_converse.bare_jid) + const own_devices = own_list.devices; devices = [...own_devices.models, ...their_devices.models]; } // Filter out our own device