move sessionprotocol to a full static class

pull/1166/head
Audric Ackermann 5 years ago
parent a92f4ab8da
commit 15f71cb9c8
No known key found for this signature in database
GPG Key ID: 999F434D76324AD4

@ -5,248 +5,259 @@ import { createOrUpdateItem, getItemById } from '../../../js/modules/data';
interface StringToNumberMap { interface StringToNumberMap {
[key: string]: number; [key: string]: number;
} }
// tslint:disable: function-name
// tslint:disable: no-unnecessary-class
export class SessionProtocol {
private static dbLoaded: Boolean = false;
/**
* This map olds the sent session timestamps, i.e. session requests message effectively sent to the recipient.
* It is backed by a database entry so it's loaded from db on startup.
* This map should not be used directly, but instead through
* `updateSendSessionTimestamp()`, `getSendSessionRequest()` or `hasSendSessionRequest()`
*/
private static sentSessionsTimestamp: StringToNumberMap;
/**
* This map olds the processed session timestamps, i.e. when we received a session request and handled it.
* It is backed by a database entry so it's loaded from db on startup.
* This map should not be used directly, but instead through
* `updateProcessedSessionTimestamp()`, `getProcessedSessionRequest()` or `hasProcessedSessionRequest()`
*/
private static processedSessionsTimestamp: StringToNumberMap;
/**
* This map olds the timestamp on which a sent session reset is triggered for a specific device.
* Once the message is sent or failed to sent, this device is removed from here.
* This is a memory only map. Which means that on app restart it's starts empty.
*/
private static readonly pendingSendSessionsTimestamp: Set<string> = new Set();
/** Returns true if we already have a session with that device */
public static async hasSession(device: string): Promise<boolean> {
// Session does not use the concept of a deviceId, thus it's always 1
const address = new window.libsignal.SignalProtocolAddress(device, 1);
const sessionCipher = new window.libsignal.SessionCipher(
window.textsecure.storage.protocol,
address
);
/** return sessionCipher.hasOpenSession();
* This map olds the sent session timestamps, i.e. session requests message effectively sent to the recipient. }
* It is backed by a database entry so it's loaded from db on startup.
* This map should not be used directly, but instead through
* `_updateSendSessionTimestamp()`, `_getSendSessionRequest()` or `_hasSendSessionRequest()`
*/
let sentSessionsTimestamp: StringToNumberMap;
/**
* This map olds the processed session timestamps, i.e. when we received a session request and handled it.
* It is backed by a database entry so it's loaded from db on startup.
* This map should not be used directly, but instead through
* `_updateProcessedSessionTimestamp()`, `_getProcessedSessionRequest()` or `_hasProcessedSessionRequest()`
*/
let processedSessionsTimestamp: StringToNumberMap;
/**
* This map olds the timestamp on which a sent session reset is triggered for a specific device.
* Once the message is sent or failed to sent, this device is removed from here.
* This is a memory only map. Which means that on app restart it's starts empty.
*/
const pendingSendSessionsTimestamp: Set<string> = new Set();
/** ======= exported functions ======= */
/** Returns true if we already have a session with that device */
export async function hasSession(device: string): Promise<boolean> {
// Session does not use the concept of a deviceId, thus it's always 1
const address = new window.libsignal.SignalProtocolAddress(device, 1);
const sessionCipher = new window.libsignal.SessionCipher(
window.textsecure.storage.protocol,
address
);
return sessionCipher.hasOpenSession();
}
/**
* Returns true if we sent a session request to that device already OR
* if a session request to that device is right now being sent.
*/
export async function hasSentSessionRequest(device: string): Promise<boolean> {
const pendingSend = pendingSendSessionsTimestamp.has(device);
const hasSent = await _hasSentSessionRequest(device);
return pendingSend || hasSent; /**
} * Returns true if we sent a session request to that device already OR
* if a session request to that device is right now being sent.
*/
public static async hasSentSessionRequest(device: string): Promise<boolean> {
const pendingSend = SessionProtocol.pendingSendSessionsTimestamp.has(device);
const hasSent = await SessionProtocol._hasSentSessionRequest(device);
/** return pendingSend || hasSent;
* Triggers a SessionResetMessage to be sent if: }
* - we do not already have a session and
* - we did not sent a session request already to that device and
* - we do not have a session request currently being send to that device
*/
export async function sendSessionRequestIfNeeded(
device: string
): Promise<void> {
if (hasSession(device) || hasSentSessionRequest(device)) {
return Promise.resolve();
}
const preKeyBundle = await window.libloki.storage.getPreKeyBundleForContact(
device
);
const sessionReset = new SessionResetMessage({
preKeyBundle,
timestamp: Date.now(),
});
return sendSessionRequest(sessionReset, device);
}
/** */ /**
export async function sendSessionRequest( * Triggers a SessionResetMessage to be sent if:
message: SessionResetMessage, * - we do not already have a session and
device: string * - we did not sent a session request already to that device and
): Promise<void> { * - we do not have a session request currently being send to that device
const timestamp = Date.now(); */
public static async sendSessionRequestIfNeeded(
// mark the session as being pending send with current timestamp device: string
// so we know we already triggered a new session with that device ): Promise<void> {
pendingSendSessionsTimestamp.add(device); if (SessionProtocol.hasSession(device) || SessionProtocol.hasSentSessionRequest(device)) {
// const rawMessage = toRawMessage(message); return Promise.resolve();
// // TODO: Send out the request via MessageSender }
// try {
// await MessageSender.send(rawMessage);
// await _updateSentSessionTimestamp(device, timestamp);
// } catch (e) {
// window.console.log('Failed to send session request to', device);
// } finally {
// pendingSendSessionsTimestamp.delete(device);
// }
}
/** const preKeyBundle = await window.libloki.storage.getPreKeyBundleForContact(
* Called when a session is establish so we store on database this info. device
*/ );
export async function onSessionEstablished(device: string) { const sessionReset = new SessionResetMessage({
// remove our existing sent timestamp for that device preKeyBundle,
return _updateSentSessionTimestamp(device, undefined); timestamp: Date.now(),
} });
export async function shouldProcessSessionRequest( return SessionProtocol.sendSessionRequest(sessionReset, device);
device: string, }
messageTimestamp: number
): Promise<boolean> {
const existingSentTimestamp = (await _getSentSessionRequest(device)) || 0;
const existingProcessedTimestamp =
(await _getProcessedSessionRequest(device)) || 0;
return (
messageTimestamp > existingSentTimestamp &&
messageTimestamp > existingProcessedTimestamp
);
}
export async function onSessionRequestProcessed(device: string) { /** */
return _updateProcessedSessionTimestamp(device, Date.now()); public static async sendSessionRequest(
} message: SessionResetMessage,
device: string
): Promise<void> {
const timestamp = Date.now();
// mark the session as being pending send with current timestamp
// so we know we already triggered a new session with that device
SessionProtocol.pendingSendSessionsTimestamp.add(device);
// const rawMessage = toRawMessage(message);
// // TODO: Send out the request via MessageSender
// try {
// await MessageSender.send(rawMessage);
// await SessionProtocolupdateSentSessionTimestamp(device, timestamp);
// } catch (e) {
// window.console.log('Failed to send session request to', device);
// } finally {
// SessionProtocolpendingSendSessionsTimestamp.delete(device);
// }
}
/** ======= local / utility functions ======= */ /**
* Called when a session is establish so we store on database this info.
*/
public static async onSessionEstablished(device: string) {
// remove our existing sent timestamp for that device
return SessionProtocol.updateSentSessionTimestamp(device, undefined);
}
/** public static async shouldProcessSessionRequest(
* We only need to fetch once from the database, because we are the only one writing to it device: string,
*/ messageTimestamp: number
async function _fetchFromDBIfNeeded(): Promise<void> { ): Promise<boolean> {
if (!sentSessionsTimestamp) { const existingSentTimestamp = (await SessionProtocol.getSentSessionRequest(device)) || 0;
const sentItem = await getItemById( const existingProcessedTimestamp =
'sentSessionsTimestamp' (await SessionProtocol.getProcessedSessionRequest(device)) || 0;
return (
messageTimestamp > existingSentTimestamp &&
messageTimestamp > existingProcessedTimestamp
); );
if (sentItem) { }
sentSessionsTimestamp = sentItem.value;
} else {
sentSessionsTimestamp = {};
}
const processedItem = await getItemById( public static async onSessionRequestProcessed(device: string) {
'processedSessionsTimestamp' return SessionProtocol.updateProcessedSessionTimestamp(device, Date.now());
); }
if (processedItem) {
processedSessionsTimestamp = processedItem.value; public static reset() {
} else { SessionProtocol.dbLoaded = false;
processedSessionsTimestamp = {}; SessionProtocol.sentSessionsTimestamp = {};
SessionProtocol.processedSessionsTimestamp = {};
}
/**
* We only need to fetch once from the database, because we are the only one writing to it
*/
private static async fetchFromDBIfNeeded(): Promise<void> {
if (!SessionProtocol.dbLoaded) {
const sentItem = await getItemById(
'sentSessionsTimestamp'
);
if (sentItem) {
SessionProtocol.sentSessionsTimestamp = sentItem.value;
} else {
SessionProtocol.sentSessionsTimestamp = {};
}
const processedItem = await getItemById(
'processedSessionsTimestamp'
);
if (processedItem) {
SessionProtocol.processedSessionsTimestamp = processedItem.value;
} else {
SessionProtocol.processedSessionsTimestamp = {};
}
SessionProtocol.dbLoaded = true;
} }
} }
}
async function _writeToDBSentSessions(): Promise<void> { private static async writeToDBSentSessions(): Promise<void> {
const data = { const data = {
id: 'sentSessionsTimestamp', id: 'sentSessionsTimestamp',
value: JSON.stringify(sentSessionsTimestamp), value: JSON.stringify(SessionProtocol.sentSessionsTimestamp),
}; };
await createOrUpdateItem(data); await createOrUpdateItem(data);
} }
async function _writeToDBProcessedSessions(): Promise<void> { private static async writeToDBProcessedSessions(): Promise<void> {
const data = { const data = {
id: 'processedSessionsTimestamp', id: 'processedSessionsTimestamp',
value: JSON.stringify(processedSessionsTimestamp), value: JSON.stringify(SessionProtocol.processedSessionsTimestamp),
}; };
await createOrUpdateItem(data); await createOrUpdateItem(data);
} }
/** /**
* This is a utility function to avoid duplicated code of _updateSentSessionTimestamp and _updateProcessedSessionTimestamp * This is a utility function to avoid duplicated code of updateSentSessionTimestamp and updateProcessedSessionTimestamp
*/ */
async function _updateSessionTimestamp( private static async updateSessionTimestamp(
device: string, device: string,
timestamp: number | undefined, timestamp: number | undefined,
map: StringToNumberMap map: StringToNumberMap
): Promise<boolean> { ): Promise<boolean> {
await _fetchFromDBIfNeeded(); await SessionProtocol.fetchFromDBIfNeeded();
if (!timestamp) { if (!timestamp) {
if (!!map[device]) { if (!!map[device]) {
delete map.device; delete map.device;
// FIXME double check how are args handle in ts (by ref/value)
return true; return true;
}
return false;
} }
map[device] = timestamp;
return false; return true;
} }
map[device] = timestamp;
return true; /**
} *
* @param device the device id
/** * @param timestamp undefined to remove the key/value pair, otherwise updates the sent timestamp and write to DB
* */
* @param device the device id private static async updateSentSessionTimestamp(
* @param timestamp undefined to remove the key/value pair, otherwise updates the sent timestamp and write to DB device: string,
*/ timestamp: number | undefined
async function _updateSentSessionTimestamp( ): Promise<void> {
device: string, if (SessionProtocol.updateSessionTimestamp(device, timestamp, SessionProtocol.sentSessionsTimestamp)) {
timestamp: number | undefined await SessionProtocol.writeToDBSentSessions();
): Promise<void> { }
if (_updateSessionTimestamp(device, timestamp, sentSessionsTimestamp)) {
await _writeToDBSentSessions();
} }
}
/** /**
* timestamp undefined to remove the key/value pair, otherwise updates the processed timestamp and writes to DB * timestamp undefined to remove the key/value pair, otherwise updates the processed timestamp and writes to DB
*/ */
async function _updateProcessedSessionTimestamp( private static async updateProcessedSessionTimestamp(
device: string, device: string,
timestamp: number | undefined timestamp: number | undefined
): Promise<void> { ): Promise<void> {
if (_updateSessionTimestamp(device, timestamp, processedSessionsTimestamp)) { if (SessionProtocol.updateSessionTimestamp(device, timestamp, SessionProtocol.processedSessionsTimestamp)) {
await _writeToDBProcessedSessions(); await SessionProtocol.writeToDBProcessedSessions();
}
} }
}
/** /**
* This is a utility function to avoid duplicate code between `_getProcessedSessionRequest()` and `_getSentSessionRequest()` * This is a utility function to avoid duplicate code between `getProcessedSessionRequest()` and `getSentSessionRequest()`
*/ */
async function _getSessionRequest( private static async getSessionRequest(
device: string, device: string,
map: StringToNumberMap map: StringToNumberMap
): Promise<number | undefined> { ): Promise<number | undefined> {
await _fetchFromDBIfNeeded(); await SessionProtocol.fetchFromDBIfNeeded();
return map[device]; return map[device];
} }
async function _getSentSessionRequest( private static async getSentSessionRequest(
device: string device: string
): Promise<number | undefined> { ): Promise<number | undefined> {
return _getSessionRequest(device, sentSessionsTimestamp); return SessionProtocol.getSessionRequest(device, SessionProtocol.sentSessionsTimestamp);
} }
async function _getProcessedSessionRequest( private static async getProcessedSessionRequest(
device: string device: string
): Promise<number | undefined> { ): Promise<number | undefined> {
return _getSessionRequest(device, processedSessionsTimestamp); return SessionProtocol.getSessionRequest(device, SessionProtocol.processedSessionsTimestamp);
} }
async function _hasSentSessionRequest(device: string): Promise<boolean> { private static async _hasSentSessionRequest(device: string): Promise<boolean> {
await _fetchFromDBIfNeeded(); await SessionProtocol.fetchFromDBIfNeeded();
return !!sentSessionsTimestamp[device]; return !!SessionProtocol.sentSessionsTimestamp[device];
}
} }

@ -1,4 +1,4 @@
import * as SessionProtocol from './SessionProtocol'; import {SessionProtocol} from './SessionProtocol';
import * as MultiDeviceProtocol from './MultiDeviceProtocol'; import * as MultiDeviceProtocol from './MultiDeviceProtocol';
export { SessionProtocol, MultiDeviceProtocol }; export { SessionProtocol, MultiDeviceProtocol };

Loading…
Cancel
Save