|
|
|
@ -5,248 +5,259 @@ import { createOrUpdateItem, getItemById } from '../../../js/modules/data';
|
|
|
|
|
interface StringToNumberMap {
|
|
|
|
|
[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
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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 sessionCipher.hasOpenSession();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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);
|
|
|
|
|
}
|
|
|
|
|
return pendingSend || hasSent;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** */
|
|
|
|
|
export async function 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
|
|
|
|
|
pendingSendSessionsTimestamp.add(device);
|
|
|
|
|
// const rawMessage = toRawMessage(message);
|
|
|
|
|
// // 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);
|
|
|
|
|
// }
|
|
|
|
|
}
|
|
|
|
|
/**
|
|
|
|
|
* 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
|
|
|
|
|
*/
|
|
|
|
|
public static async sendSessionRequestIfNeeded(
|
|
|
|
|
device: string
|
|
|
|
|
): Promise<void> {
|
|
|
|
|
if (SessionProtocol.hasSession(device) || SessionProtocol.hasSentSessionRequest(device)) {
|
|
|
|
|
return Promise.resolve();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Called when a session is establish so we store on database this info.
|
|
|
|
|
*/
|
|
|
|
|
export async function onSessionEstablished(device: string) {
|
|
|
|
|
// remove our existing sent timestamp for that device
|
|
|
|
|
return _updateSentSessionTimestamp(device, undefined);
|
|
|
|
|
}
|
|
|
|
|
const preKeyBundle = await window.libloki.storage.getPreKeyBundleForContact(
|
|
|
|
|
device
|
|
|
|
|
);
|
|
|
|
|
const sessionReset = new SessionResetMessage({
|
|
|
|
|
preKeyBundle,
|
|
|
|
|
timestamp: Date.now(),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export async function shouldProcessSessionRequest(
|
|
|
|
|
device: string,
|
|
|
|
|
messageTimestamp: number
|
|
|
|
|
): Promise<boolean> {
|
|
|
|
|
const existingSentTimestamp = (await _getSentSessionRequest(device)) || 0;
|
|
|
|
|
const existingProcessedTimestamp =
|
|
|
|
|
(await _getProcessedSessionRequest(device)) || 0;
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
messageTimestamp > existingSentTimestamp &&
|
|
|
|
|
messageTimestamp > existingProcessedTimestamp
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
return SessionProtocol.sendSessionRequest(sessionReset, device);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* We only need to fetch once from the database, because we are the only one writing to it
|
|
|
|
|
*/
|
|
|
|
|
async function _fetchFromDBIfNeeded(): Promise<void> {
|
|
|
|
|
if (!sentSessionsTimestamp) {
|
|
|
|
|
const sentItem = await getItemById(
|
|
|
|
|
'sentSessionsTimestamp'
|
|
|
|
|
public static async shouldProcessSessionRequest(
|
|
|
|
|
device: string,
|
|
|
|
|
messageTimestamp: number
|
|
|
|
|
): Promise<boolean> {
|
|
|
|
|
const existingSentTimestamp = (await SessionProtocol.getSentSessionRequest(device)) || 0;
|
|
|
|
|
const existingProcessedTimestamp =
|
|
|
|
|
(await SessionProtocol.getProcessedSessionRequest(device)) || 0;
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
messageTimestamp > existingSentTimestamp &&
|
|
|
|
|
messageTimestamp > existingProcessedTimestamp
|
|
|
|
|
);
|
|
|
|
|
if (sentItem) {
|
|
|
|
|
sentSessionsTimestamp = sentItem.value;
|
|
|
|
|
} else {
|
|
|
|
|
sentSessionsTimestamp = {};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const processedItem = await getItemById(
|
|
|
|
|
'processedSessionsTimestamp'
|
|
|
|
|
);
|
|
|
|
|
if (processedItem) {
|
|
|
|
|
processedSessionsTimestamp = processedItem.value;
|
|
|
|
|
} else {
|
|
|
|
|
processedSessionsTimestamp = {};
|
|
|
|
|
public static async onSessionRequestProcessed(device: string) {
|
|
|
|
|
return SessionProtocol.updateProcessedSessionTimestamp(device, Date.now());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static reset() {
|
|
|
|
|
SessionProtocol.dbLoaded = false;
|
|
|
|
|
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> {
|
|
|
|
|
const data = {
|
|
|
|
|
id: 'sentSessionsTimestamp',
|
|
|
|
|
value: JSON.stringify(sentSessionsTimestamp),
|
|
|
|
|
};
|
|
|
|
|
private static async writeToDBSentSessions(): Promise<void> {
|
|
|
|
|
const data = {
|
|
|
|
|
id: 'sentSessionsTimestamp',
|
|
|
|
|
value: JSON.stringify(SessionProtocol.sentSessionsTimestamp),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
await createOrUpdateItem(data);
|
|
|
|
|
}
|
|
|
|
|
await createOrUpdateItem(data);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function _writeToDBProcessedSessions(): Promise<void> {
|
|
|
|
|
const data = {
|
|
|
|
|
id: 'processedSessionsTimestamp',
|
|
|
|
|
value: JSON.stringify(processedSessionsTimestamp),
|
|
|
|
|
};
|
|
|
|
|
private static async writeToDBProcessedSessions(): Promise<void> {
|
|
|
|
|
const data = {
|
|
|
|
|
id: '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
|
|
|
|
|
*/
|
|
|
|
|
async function _updateSessionTimestamp(
|
|
|
|
|
device: string,
|
|
|
|
|
timestamp: number | undefined,
|
|
|
|
|
map: StringToNumberMap
|
|
|
|
|
): Promise<boolean> {
|
|
|
|
|
await _fetchFromDBIfNeeded();
|
|
|
|
|
if (!timestamp) {
|
|
|
|
|
if (!!map[device]) {
|
|
|
|
|
delete map.device;
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
/**
|
|
|
|
|
* This is a utility function to avoid duplicated code of updateSentSessionTimestamp and updateProcessedSessionTimestamp
|
|
|
|
|
*/
|
|
|
|
|
private static async updateSessionTimestamp(
|
|
|
|
|
device: string,
|
|
|
|
|
timestamp: number | undefined,
|
|
|
|
|
map: StringToNumberMap
|
|
|
|
|
): Promise<boolean> {
|
|
|
|
|
await SessionProtocol.fetchFromDBIfNeeded();
|
|
|
|
|
if (!timestamp) {
|
|
|
|
|
if (!!map[device]) {
|
|
|
|
|
delete map.device;
|
|
|
|
|
// FIXME double check how are args handle in ts (by ref/value)
|
|
|
|
|
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
|
|
|
|
|
*/
|
|
|
|
|
async function _updateSentSessionTimestamp(
|
|
|
|
|
device: string,
|
|
|
|
|
timestamp: number | undefined
|
|
|
|
|
): Promise<void> {
|
|
|
|
|
if (_updateSessionTimestamp(device, timestamp, sentSessionsTimestamp)) {
|
|
|
|
|
await _writeToDBSentSessions();
|
|
|
|
|
/**
|
|
|
|
|
*
|
|
|
|
|
* @param device the device id
|
|
|
|
|
* @param timestamp undefined to remove the key/value pair, otherwise updates the sent timestamp and write to DB
|
|
|
|
|
*/
|
|
|
|
|
private static async updateSentSessionTimestamp(
|
|
|
|
|
device: string,
|
|
|
|
|
timestamp: number | undefined
|
|
|
|
|
): Promise<void> {
|
|
|
|
|
if (SessionProtocol.updateSessionTimestamp(device, timestamp, SessionProtocol.sentSessionsTimestamp)) {
|
|
|
|
|
await SessionProtocol.writeToDBSentSessions();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* timestamp undefined to remove the key/value pair, otherwise updates the processed timestamp and writes to DB
|
|
|
|
|
*/
|
|
|
|
|
async function _updateProcessedSessionTimestamp(
|
|
|
|
|
device: string,
|
|
|
|
|
timestamp: number | undefined
|
|
|
|
|
): Promise<void> {
|
|
|
|
|
if (_updateSessionTimestamp(device, timestamp, processedSessionsTimestamp)) {
|
|
|
|
|
await _writeToDBProcessedSessions();
|
|
|
|
|
/**
|
|
|
|
|
* timestamp undefined to remove the key/value pair, otherwise updates the processed timestamp and writes to DB
|
|
|
|
|
*/
|
|
|
|
|
private static async updateProcessedSessionTimestamp(
|
|
|
|
|
device: string,
|
|
|
|
|
timestamp: number | undefined
|
|
|
|
|
): Promise<void> {
|
|
|
|
|
if (SessionProtocol.updateSessionTimestamp(device, timestamp, SessionProtocol.processedSessionsTimestamp)) {
|
|
|
|
|
await SessionProtocol.writeToDBProcessedSessions();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* This is a utility function to avoid duplicate code between `_getProcessedSessionRequest()` and `_getSentSessionRequest()`
|
|
|
|
|
*/
|
|
|
|
|
async function _getSessionRequest(
|
|
|
|
|
device: string,
|
|
|
|
|
map: StringToNumberMap
|
|
|
|
|
): Promise<number | undefined> {
|
|
|
|
|
await _fetchFromDBIfNeeded();
|
|
|
|
|
/**
|
|
|
|
|
* This is a utility function to avoid duplicate code between `getProcessedSessionRequest()` and `getSentSessionRequest()`
|
|
|
|
|
*/
|
|
|
|
|
private static async getSessionRequest(
|
|
|
|
|
device: string,
|
|
|
|
|
map: StringToNumberMap
|
|
|
|
|
): Promise<number | undefined> {
|
|
|
|
|
await SessionProtocol.fetchFromDBIfNeeded();
|
|
|
|
|
|
|
|
|
|
return map[device];
|
|
|
|
|
}
|
|
|
|
|
return map[device];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function _getSentSessionRequest(
|
|
|
|
|
device: string
|
|
|
|
|
): Promise<number | undefined> {
|
|
|
|
|
return _getSessionRequest(device, sentSessionsTimestamp);
|
|
|
|
|
}
|
|
|
|
|
private static async getSentSessionRequest(
|
|
|
|
|
device: string
|
|
|
|
|
): Promise<number | undefined> {
|
|
|
|
|
return SessionProtocol.getSessionRequest(device, SessionProtocol.sentSessionsTimestamp);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function _getProcessedSessionRequest(
|
|
|
|
|
device: string
|
|
|
|
|
): Promise<number | undefined> {
|
|
|
|
|
return _getSessionRequest(device, processedSessionsTimestamp);
|
|
|
|
|
}
|
|
|
|
|
private static async getProcessedSessionRequest(
|
|
|
|
|
device: string
|
|
|
|
|
): Promise<number | undefined> {
|
|
|
|
|
return SessionProtocol.getSessionRequest(device, SessionProtocol.processedSessionsTimestamp);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function _hasSentSessionRequest(device: string): Promise<boolean> {
|
|
|
|
|
await _fetchFromDBIfNeeded();
|
|
|
|
|
private static async _hasSentSessionRequest(device: string): Promise<boolean> {
|
|
|
|
|
await SessionProtocol.fetchFromDBIfNeeded();
|
|
|
|
|
|
|
|
|
|
return !!sentSessionsTimestamp[device];
|
|
|
|
|
return !!SessionProtocol.sentSessionsTimestamp[device];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|