@jsxc/jsxc
Version:
Real-time XMPP chat application with video calls, file transfer and encrypted communication
409 lines (301 loc) • 11.9 kB
text/typescript
import IStorage from '../../../Storage.interface';
import ArrayBufferUtils from '../util/ArrayBuffer';
import { Trust } from './Device';
import IdentityKey from '../model/IdentityKey';
import SignedPreKey from '../model/SignedPreKey';
import PreKey from '../model/PreKey';
import Address from '../vendor/Address';
import SignalStore, { DIRECTION } from '../vendor/SignalStore';
const PREFIX = 'store';
const PREFIX_SESSION = 'session:';
const PREFIX_IDENTITY_KEY = 'identityKey:';
const PREFIX_PREKEY = '25519KeypreKey:';
const PREFIX_SIGNED_PREKEY = '25519KeysignedKey:';
const PREFIX_TRUST = 'trust:';
const PREFIX_DEVICE_LIST = 'deviceList:';
const PREFIX_DEVICE_USED = 'deviceUsed:';
const KEY_DISABLED_DEVICES = 'disabledDevices';
export default class Store {
private signalStore: SignalStore;
constructor(private storage: IStorage) {}
public getSignalStore(): SignalStore {
if (!this.signalStore) {
this.signalStore = new SignalStore(this);
}
return this.signalStore;
}
private put(key: string, value: any): void {
if (typeof key === 'undefined' || typeof value === 'undefined' || key === null || value === null) {
throw new Error('I will not store undefined or null');
}
let stringified = JSON.stringify(value, function (key, value) {
if (value instanceof ArrayBuffer) {
return 'stringifiedArrayBuffer|' + JSON.stringify(ArrayBufferUtils.toArray(value));
}
return value;
});
this.storage.setItem(PREFIX, key, { v: stringified });
}
private get(key: string, defaultValue?: any): any {
if (typeof key === 'undefined' || key === null) {
throw new Error('I cant get a value for a undefined or null key');
}
let data = this.storage.getItem(PREFIX, key);
if (data) {
return JSON.parse(data.v, function (key, value) {
if (/^stringifiedArrayBuffer\|/.test(value)) {
let cleaned = value.replace(/^stringifiedArrayBuffer\|/, '');
return ArrayBufferUtils.fromArray(JSON.parse(cleaned));
}
return value;
});
}
return defaultValue;
}
private remove(key: string): void {
if (typeof key === 'undefined' || key === null) {
throw new Error('I cant remove null or undefined key');
}
this.storage.removeItem(PREFIX, key);
}
public isReady(): boolean {
return this.get('deviceId') && this.get('deviceName') && this.get('identityKey') && this.get('registrationId');
}
public getPublishedVersion(): number {
return parseInt(this.get('publishedVersion', 0), 10);
}
public isPublished(): boolean {
return this.get('published') === 'true' || this.get('published') === true;
}
public setPublished(published: boolean) {
if (published) {
this.put('publishedVersion', 1);
}
return this.put('published', published);
}
public getDeviceList(deviceName: string): number[] {
return this.get(PREFIX_DEVICE_LIST + deviceName, []);
}
public setDeviceList(deviceName: string, deviceList: number[]) {
this.put(PREFIX_DEVICE_LIST + deviceName, deviceList);
}
public setPeerUsed(deviceName: string) {
this.put(PREFIX_DEVICE_USED + deviceName, true);
}
public isPeerUsed(deviceName: string): boolean {
let value = this.get(PREFIX_DEVICE_USED + deviceName);
return typeof value === 'boolean' ? value : false;
}
public setLocalDeviceId(id: number) {
this.put('deviceId', id);
}
public getLocalDeviceId(): number {
return parseInt(this.get('deviceId'), 10);
}
public setLocalDeviceName(name: string) {
this.put('deviceName', name);
}
public getLocalDeviceName(): string {
return this.get('deviceName');
}
public getLocalDeviceAddress(): Address {
let name = this.getLocalDeviceName();
let id = this.getLocalDeviceId();
if (!name || !id) {
throw new Error('Local device address not generated yet');
}
return new Address(name, id);
}
public setLocalRegistrationId(id: number) {
this.put('registrationId', id);
}
public getLocalRegistrationId(): number {
return parseInt(this.get('registrationId'), 10);
}
public setLocalIdentityKey(identityKey: IdentityKey) {
let address = this.getLocalDeviceAddress();
// we have to save it twice to not loose the private key part
this.put('identityKey', identityKey.export());
this.saveIdentity(address, identityKey);
this.setTrust(address, Trust.confirmed);
}
public getLocalIdentityKey(): IdentityKey {
let data = this.get('identityKey');
if (!data) {
throw new Error('Found no local identity key');
}
return new IdentityKey(data);
}
public disable(identifier: Address) {
let disabledDevices: number[] = this.storage.getItem(KEY_DISABLED_DEVICES, identifier.getName()) || [];
if (disabledDevices.indexOf(identifier.getDeviceId()) < 0) {
disabledDevices.push(identifier.getDeviceId());
this.storage.setItem(KEY_DISABLED_DEVICES, identifier.getName(), disabledDevices);
}
}
public enable(identifier: Address) {
let disabledDevices: number[] = this.storage.getItem(KEY_DISABLED_DEVICES, identifier.getName()) || [];
let index = disabledDevices.indexOf(identifier.getDeviceId());
if (index > -1) {
disabledDevices.splice(index, 1);
this.storage.setItem(KEY_DISABLED_DEVICES, identifier.getName(), disabledDevices);
}
}
public isDisabled(identifier: Address) {
let disabledDevices: number[] = this.storage.getItem(KEY_DISABLED_DEVICES, identifier.getName()) || [];
return disabledDevices.indexOf(identifier.getDeviceId()) > -1;
}
public getTrust(identifier: Address): Trust {
let trustMatrix = this.getTrustMatrix(identifier);
let fingerprint = this.getFingerprint(identifier);
if (fingerprint && trustMatrix[Trust.confirmed].indexOf(fingerprint) >= 0) {
return Trust.confirmed;
}
if (fingerprint && trustMatrix[Trust.recognized].indexOf(fingerprint) >= 0) {
return Trust.recognized;
}
if (fingerprint && trustMatrix[Trust.ignored].indexOf(fingerprint) >= 0) {
return Trust.ignored;
}
return Trust.unknown;
}
public setTrust(identifier: Address, trust: Trust) {
let trustMatrix = this.getTrustMatrix(identifier);
let fingerprint = this.getFingerprint(identifier);
if (!fingerprint) {
throw new Error('Can not trust without a fingerprint');
}
for (let trustLevel in trustMatrix) {
trustMatrix[trustLevel] = trustMatrix[trustLevel].filter(fp => fp !== fingerprint);
}
trustMatrix[trust].push(fingerprint);
this.put(PREFIX_TRUST + identifier.getName(), trustMatrix);
return fingerprint;
}
private getTrustMatrix(identifier: Address) {
let trustMatrix = this.get(PREFIX_TRUST + identifier.getName()) || {
[Trust.confirmed]: [],
[Trust.recognized]: [],
[Trust.ignored]: [],
};
return trustMatrix;
}
public getFingerprint(identifier: Address): string | undefined {
let identityKey = this.getIdentityKey(identifier);
return identityKey ? identityKey.getFingerprint() : undefined;
}
public isTrustedIdentity(identifier: Address, identityKey: IdentityKey, direction: number): Promise<boolean> {
let fingerprint = identityKey.getFingerprint();
let trustMatrix = this.getTrustMatrix(identifier);
if (
trustMatrix[Trust.confirmed].indexOf(fingerprint) > -1 ||
trustMatrix[Trust.recognized].indexOf(fingerprint) > -1 ||
direction === DIRECTION.RECEIVING
) {
return Promise.resolve(true);
}
return Promise.resolve(false);
}
public saveIdentity(identifier: Address, identityKey: IdentityKey): Promise<boolean> {
let existing = this.getIdentityKey(identifier);
this.setIdentityKey(identifier, identityKey);
return Promise.resolve(existing && ArrayBufferUtils.isEqual(identityKey.getPublic(), existing.getPublic()));
}
public getIdentityKey(identifier: Address): IdentityKey | undefined {
let data = this.get(PREFIX_IDENTITY_KEY + identifier.toString());
if (data) {
return new IdentityKey(data);
}
}
private setIdentityKey(identifier: Address, identityKey: IdentityKey) {
this.put(PREFIX_IDENTITY_KEY + identifier.toString(), identityKey.export());
}
public getPreKeyIds(): number[] {
return this.get('preKeyIds') || [];
}
public getAllPreKeys(): PreKey[] {
let preKeyIds = this.get('preKeyIds') || [];
return preKeyIds.map(id => this.getPreKey(id));
}
public getNumberOfPreKeys(): number {
let preKeyIds = this.get('preKeyIds') || [];
return preKeyIds.length;
}
public getPreKey(keyId: number): undefined | PreKey {
let preKey;
let data = this.get(PREFIX_PREKEY + keyId);
if (data !== undefined) {
preKey = new PreKey(data);
}
return preKey;
}
public storePreKey(preKey: PreKey) {
let preKeyIds = this.get('preKeyIds') || [];
if (preKeyIds.indexOf(preKey.getId()) < 0) {
preKeyIds.push(preKey.getId());
this.put('preKeyIds', preKeyIds);
}
this.put(PREFIX_PREKEY + preKey.getId(), preKey.export());
}
public removePreKey(keyId: number): Promise<void> {
let preKeyIds: number[] = this.get('preKeyIds') || [];
this.put(
'preKeyIds',
preKeyIds.filter(id => id !== keyId)
);
return Promise.resolve(this.remove(PREFIX_PREKEY + keyId));
}
public getSignedPreKeyIds(): number[] {
return this.get('signedPreKeyIds') || [];
}
public getSignedPreKey(keyId: number): undefined | SignedPreKey {
let signedPreKey;
let data = this.get(PREFIX_SIGNED_PREKEY + keyId);
if (data !== undefined) {
signedPreKey = new SignedPreKey(data);
}
return signedPreKey;
}
public storeSignedPreKey(signedPreKey: SignedPreKey) {
let ids = this.get('signedPreKeyIds') || [];
if (ids.indexOf(signedPreKey.getId()) < 0) {
ids.push(signedPreKey.getId());
this.put('signedPreKeyIds', ids);
}
this.put(PREFIX_SIGNED_PREKEY + signedPreKey.getId(), signedPreKey.export());
}
public removeSignedPreKey(keyId: number): Promise<void> {
let ids: number[] = this.get('signedPreKeyIds') || [];
this.put(
'signedPreKeyIds',
ids.filter(id => id !== keyId)
);
return Promise.resolve(this.remove(PREFIX_SIGNED_PREKEY + keyId));
}
public loadSession(identifier: string): Promise<string | undefined> {
return Promise.resolve(this.get(PREFIX_SESSION + identifier));
}
public storeSession(identifier: string, session: string): Promise<void> {
return Promise.resolve(this.put(PREFIX_SESSION + identifier, session));
}
public removeSession(identifier: string): Promise<void> {
return Promise.resolve(this.remove(PREFIX_SESSION + identifier));
}
public removeAllSessions(identifier: string) {
//@TODO implement removeAllSessions
return Promise.resolve();
}
public setLastUsed(identifier: Address) {
this.put('lastUsed:' + identifier.toString(), new Date());
}
public getLastUsed(identifier: Address) {
let used = this.get('lastUsed:' + identifier.toString());
return used ? new Date(used) : undefined;
}
/**
* Helper functions
*/
public hasSession(identifier: string): boolean {
return !!this.get(PREFIX_SESSION + identifier);
}
}