@jsxc/jsxc
Version:
Real-time XMPP chat application with video calls, file transfer and encrypted communication
144 lines (104 loc) • 4.45 kB
text/typescript
import PEP from '@connection/services/PEP';
import Address from '../vendor/Address';
import { NS_BUNDLES, NS_BASE, NS_DEVICELIST, NUM_PRE_KEYS, MAX_PRE_KEY_ID } from '../util/Const';
import Bundle from './Bundle';
import Log from '@util/Log';
import IdentityKey from '../model/IdentityKey';
import PreKey from '../model/PreKey';
import SignedPreKey from '../model/SignedPreKey';
import { KeyHelper } from '../vendor/KeyHelper';
import Store from './Store';
import { $build } from '../../../vendor/Strophe';
import Random from '@util/Random';
export default class BundleManager {
constructor(private pepService: PEP, private store: Store) {}
public async refreshBundle(): Promise<Bundle> {
Log.debug('Refresh local device bundle.');
let identityKey = this.store.getLocalIdentityKey();
let preKeyIds = this.store.getPreKeyIds();
let signedPreKeyIds = this.store.getSignedPreKeyIds();
let newKeyIds = this.generateUniqueKeyIds(NUM_PRE_KEYS - preKeyIds.length, preKeyIds);
await Promise.all(newKeyIds.map(id => this.generatePreKey(id)));
if (signedPreKeyIds.length !== 1) {
throw new Error(
`Could not refresh local device bundle, because we have ${signedPreKeyIds.length} signed prekeys.`
);
}
return new Bundle({
identityKey,
signedPreKey: this.store.getSignedPreKey(signedPreKeyIds[0]),
preKeys: this.store.getAllPreKeys(),
});
}
public async generateBundle(identityKey: IdentityKey): Promise<Bundle> {
Log.debug('Generate local device bundle.');
let preKeyPromises: Promise<PreKey>[];
let ids = this.generateUniqueKeyIds(NUM_PRE_KEYS);
let signedPreKeyId = ids.pop();
preKeyPromises = ids.map(id => this.generatePreKey(id));
preKeyPromises.push(this.generateSignedPreKey(identityKey, signedPreKeyId));
let preKeys = await Promise.all(preKeyPromises);
return new Bundle({
identityKey,
signedPreKey: <SignedPreKey>preKeys.pop(),
preKeys,
});
}
private generateUniqueKeyIds(quantity: number, list: number[] = []) {
let ids = [];
while (ids.length < quantity) {
let id = Random.number(MAX_PRE_KEY_ID, 1);
if (ids.indexOf(id) < 0) {
ids.push(id);
}
}
return ids;
}
private async generatePreKey(id: number): Promise<PreKey> {
let preKey = await KeyHelper.generatePreKey(id);
this.store.storePreKey(preKey);
return preKey;
}
private async generateSignedPreKey(identityKey: IdentityKey, id: number): Promise<SignedPreKey> {
let signedPreKey = await KeyHelper.generateSignedPreKey(identityKey, id);
this.store.storeSignedPreKey(signedPreKey);
return signedPreKey;
}
public async requestBundle(address: Address): Promise<Bundle> {
let node = NS_BUNDLES + address.getDeviceId();
let stanza;
try {
stanza = await this.pepService.retrieveItems(node, address.getName());
} catch (errorStanza) {
Log.warn('Error while retrieving bundle', errorStanza);
throw new Error('Could not retrieve bundle');
}
let itemsElement = $(stanza).find(`items[node='${node}']`);
let bundleElement = itemsElement.find(`bundle[xmlns='${NS_BASE}']`);
if (bundleElement.length !== 1) {
throw new Error(`Expected to find one bundle, but there are actually ${bundleElement.length} bundles.`);
}
let bundle = Bundle.fromXML(bundleElement.get());
return bundle;
}
public async publishBundle(bundle: Bundle): Promise<void> {
let node = NS_BUNDLES + this.store.getLocalDeviceId();
await this.pepService.publish(node, bundle.toXML().tree(), 'current');
this.store.setPublished(true);
}
public publishDeviceId(deviceId: number): Promise<Element> {
let localDeviceName = this.store.getLocalDeviceName();
let deviceIds = this.store.getDeviceList(localDeviceName);
if (deviceIds.indexOf(deviceId) < 0) {
deviceIds.push(deviceId);
}
let xmlList = $build('list', { xmlns: NS_BASE });
for (let id of deviceIds) {
xmlList.c('device', { id }).up();
}
return this.pepService.publish(NS_DEVICELIST, xmlList.tree(), 'current');
}
public deleteDeviceList(): Promise<Element> {
return this.pepService.delete(NS_DEVICELIST);
}
}