xmpp.chapril.org-conversejs/src/plugins/omemo/devicelist.js
2021-07-19 16:40:40 +02:00

136 lines
4.6 KiB
JavaScript

import log from '@converse/headless/log';
import { Model } from '@converse/skeletor/src/model.js';
import { _converse, api, converse } from '@converse/headless/core';
import { initStorage } from '@converse/headless/utils/storage.js';
import { restoreOMEMOSession } from './utils.js';
const { Strophe, $build, $iq, sizzle } = converse.env;
/**
* @class
* @namespace _converse.DeviceList
* @memberOf _converse
*/
const DeviceList = Model.extend({
idAttribute: 'jid',
initialize () {
this.initDevices();
},
initDevices () {
this.devices = new _converse.Devices();
const id = `converse.devicelist-${_converse.bare_jid}-${this.get('jid')}`;
initStorage(this.devices, id);
this.fetchDevices();
},
async onDevicesFound (collection) {
if (collection.length === 0) {
let ids;
try {
ids = await this.fetchDevicesFromServer();
} catch (e) {
if (e === null) {
log.error(`Timeout error while fetching devices for ${this.get('jid')}`);
} else {
log.error(`Could not fetch devices for ${this.get('jid')}`);
log.error(e);
}
this.destroy();
}
if (this.get('jid') === _converse.bare_jid) {
await this.publishCurrentDevice(ids);
}
}
},
fetchDevices () {
if (this._devices_promise === undefined) {
this._devices_promise = new Promise(resolve => {
this.devices.fetch({
'success': c => resolve(this.onDevicesFound(c)),
'error': (m, e) => {
log.error(e);
resolve();
}
});
});
}
return this._devices_promise;
},
async getOwnDeviceId () {
let device_id = _converse.omemo_store.get('device_id');
if (!this.devices.findWhere({ 'id': device_id })) {
// Generate a new bundle if we cannot find our device
await _converse.omemo_store.generateBundle();
device_id = _converse.omemo_store.get('device_id');
}
return device_id;
},
async publishCurrentDevice (device_ids) {
if (this.get('jid') !== _converse.bare_jid) {
return; // We only publish for ourselves.
}
await restoreOMEMOSession();
if (!_converse.omemo_store) {
// Happens during tests. The connection gets torn down
// before publishCurrentDevice has time to finish.
log.warn('publishCurrentDevice: omemo_store is not defined, likely a timing issue');
return;
}
if (!device_ids.includes(await this.getOwnDeviceId())) {
return this.publishDevices();
}
},
async fetchDevicesFromServer () {
const stanza = $iq({
'type': 'get',
'from': _converse.bare_jid,
'to': this.get('jid')
})
.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 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;
},
/**
* Send an IQ stanza to the current user's "devices" PEP node to
* ensure that all devices are published for potential chat partners to see.
* See: https://xmpp.org/extensions/xep-0384.html#usecases-announcing
*/
publishDevices () {
const item = $build('item', { 'id': 'current' }).c('list', { 'xmlns': Strophe.NS.OMEMO });
this.devices.filter(d => d.get('active')).forEach(d => item.c('device', { 'id': d.get('id') }).up());
const options = { 'pubsub#access_model': 'open' };
return api.pubsub.publish(null, Strophe.NS.OMEMO_DEVICELIST, item, options, false);
},
removeOwnDevices (device_ids) {
if (this.get('jid') !== _converse.bare_jid) {
throw new Error("Cannot remove devices from someone else's device list");
}
device_ids.forEach(device_id => this.devices.get(device_id).destroy());
return this.publishDevices();
}
});
export default DeviceList;