mirror of
https://github.com/oxen-io/session-desktop.git
synced 2023-12-14 02:12:57 +01:00
Merge pull request #1723 from Bilb/poll-less-often-group-not-active
Poll less often group not active
This commit is contained in:
commit
19555b99a0
|
@ -450,11 +450,7 @@
|
||||||
}, window.CONSTANTS.NOTIFICATION_ENABLE_TIMEOUT_SECONDS * 1000);
|
}, window.CONSTANTS.NOTIFICATION_ENABLE_TIMEOUT_SECONDS * 1000);
|
||||||
|
|
||||||
window.NewReceiver.queueAllCached();
|
window.NewReceiver.queueAllCached();
|
||||||
window
|
|
||||||
.getSwarmPollingInstance()
|
|
||||||
.addPubkey(window.libsession.Utils.UserUtils.getOurPubKeyStrFromCache());
|
|
||||||
|
|
||||||
window.getSwarmPollingInstance().start();
|
|
||||||
window.libsession.Utils.AttachmentDownloads.start({
|
window.libsession.Utils.AttachmentDownloads.start({
|
||||||
logger: window.log,
|
logger: window.log,
|
||||||
});
|
});
|
||||||
|
|
|
@ -40,7 +40,7 @@
|
||||||
"format-full": "prettier --list-different --write \"*.{css,js,json,scss,ts,tsx}\" \"./**/*.{css,js,json,scss,ts,tsx}\"",
|
"format-full": "prettier --list-different --write \"*.{css,js,json,scss,ts,tsx}\" \"./**/*.{css,js,json,scss,ts,tsx}\"",
|
||||||
"transpile": "tsc --incremental",
|
"transpile": "tsc --incremental",
|
||||||
"transpile:watch": "tsc -w",
|
"transpile:watch": "tsc -w",
|
||||||
"clean-transpile": "rimraf ts/**/*.js ts/*.js ts/*.js.map ts/**/*.js.map && rimraf tsconfig.tsbuildinfo;",
|
"clean-transpile": "rimraf 'ts/**/*.js ts/*.js' 'ts/*.js.map' 'ts/**/*.js.map' && rimraf tsconfig.tsbuildinfo;",
|
||||||
"ready": "yarn clean-transpile; yarn grunt && yarn lint-full && yarn test"
|
"ready": "yarn clean-transpile; yarn grunt && yarn lint-full && yarn test"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
|
@ -245,9 +245,7 @@ const doAppStartUp = () => {
|
||||||
debounce(triggerAvatarReUploadIfNeeded, 200);
|
debounce(triggerAvatarReUploadIfNeeded, 200);
|
||||||
|
|
||||||
// TODO: Investigate the case where we reconnect
|
// TODO: Investigate the case where we reconnect
|
||||||
const ourKey = UserUtils.getOurPubKeyStrFromCache();
|
void getSwarmPollingInstance().start();
|
||||||
getSwarmPollingInstance().addPubkey(ourKey);
|
|
||||||
getSwarmPollingInstance().start();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import Electron from 'electron';
|
import Electron from 'electron';
|
||||||
|
|
||||||
const { ipcRenderer } = Electron;
|
const { ipcRenderer } = Electron;
|
||||||
// tslint:disable: function-name no-require-imports no-var-requires one-variable-per-declaration no-void-expression
|
// tslint:disable: no-require-imports no-var-requires one-variable-per-declaration no-void-expression
|
||||||
|
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { ConversationCollection, ConversationModel } from '../models/conversation';
|
import { ConversationCollection, ConversationModel } from '../models/conversation';
|
||||||
|
|
|
@ -44,6 +44,7 @@ import { NotificationForConvoOption } from '../components/conversation/Conversat
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { updateConfirmModal } from '../state/ducks/modalDialog';
|
import { updateConfirmModal } from '../state/ducks/modalDialog';
|
||||||
import { createTaskWithTimeout } from '../session/utils/TaskWithTimeout';
|
import { createTaskWithTimeout } from '../session/utils/TaskWithTimeout';
|
||||||
|
import { DURATION, SWARM_POLLING_TIMEOUT } from '../session/constants';
|
||||||
|
|
||||||
export enum ConversationTypeEnum {
|
export enum ConversationTypeEnum {
|
||||||
GROUP = 'group',
|
GROUP = 'group',
|
||||||
|
|
|
@ -17,6 +17,12 @@ export const TTL_DEFAULT = {
|
||||||
TTL_MAX: 14 * DURATION.DAYS,
|
TTL_MAX: 14 * DURATION.DAYS,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const SWARM_POLLING_TIMEOUT = {
|
||||||
|
ACTIVE: DURATION.SECONDS * 5,
|
||||||
|
MEDIUM_ACTIVE: DURATION.SECONDS * 60,
|
||||||
|
INACTIVE: DURATION.MINUTES * 60,
|
||||||
|
};
|
||||||
|
|
||||||
export const PROTOCOLS = {
|
export const PROTOCOLS = {
|
||||||
// tslint:disable-next-line: no-http-string
|
// tslint:disable-next-line: no-http-string
|
||||||
HTTP: 'http:',
|
HTTP: 'http:',
|
||||||
|
|
|
@ -116,7 +116,7 @@ export class ConversationController {
|
||||||
|
|
||||||
conversation.initialPromise = create();
|
conversation.initialPromise = create();
|
||||||
conversation.initialPromise.then(async () => {
|
conversation.initialPromise.then(async () => {
|
||||||
if (window.inboxStore) {
|
if (window?.inboxStore) {
|
||||||
window.inboxStore?.dispatch(
|
window.inboxStore?.dispatch(
|
||||||
conversationActions.conversationAdded(conversation.id, conversation.getProps())
|
conversationActions.conversationAdded(conversation.id, conversation.getProps())
|
||||||
);
|
);
|
||||||
|
@ -242,7 +242,7 @@ export class ConversationController {
|
||||||
window.log.info(`deleteContact !isPrivate, convo removed from DB: ${id}`);
|
window.log.info(`deleteContact !isPrivate, convo removed from DB: ${id}`);
|
||||||
|
|
||||||
this.conversations.remove(conversation);
|
this.conversations.remove(conversation);
|
||||||
if (window.inboxStore) {
|
if (window?.inboxStore) {
|
||||||
window.inboxStore?.dispatch(conversationActions.conversationRemoved(conversation.id));
|
window.inboxStore?.dispatch(conversationActions.conversationRemoved(conversation.id));
|
||||||
window.inboxStore?.dispatch(
|
window.inboxStore?.dispatch(
|
||||||
conversationActions.conversationChanged(conversation.id, conversation.getProps())
|
conversationActions.conversationChanged(conversation.id, conversation.getProps())
|
||||||
|
@ -310,7 +310,7 @@ export class ConversationController {
|
||||||
public reset() {
|
public reset() {
|
||||||
this._initialPromise = Promise.resolve();
|
this._initialPromise = Promise.resolve();
|
||||||
this._initialFetchComplete = false;
|
this._initialFetchComplete = false;
|
||||||
if (window.inboxStore) {
|
if (window?.inboxStore) {
|
||||||
window.inboxStore?.dispatch(conversationActions.removeAllConversations());
|
window.inboxStore?.dispatch(conversationActions.removeAllConversations());
|
||||||
}
|
}
|
||||||
this.conversations.reset([]);
|
this.conversations.reset([]);
|
||||||
|
|
|
@ -69,7 +69,6 @@ export class MessageController {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// tslint:disable-next-line: function-name
|
|
||||||
public get(identifier: string) {
|
public get(identifier: string) {
|
||||||
return this.messageLookup.get(identifier);
|
return this.messageLookup.get(identifier);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { PubKey } from '../types';
|
import { PubKey } from '../types';
|
||||||
import { getSwarmFor } from './snodePool';
|
import * as snodePool from './snodePool';
|
||||||
import { retrieveNextMessages } from './SNodeAPI';
|
import { retrieveNextMessages } from './SNodeAPI';
|
||||||
import { SignalService } from '../../protobuf';
|
import { SignalService } from '../../protobuf';
|
||||||
import * as Receiver from '../../receiver/receiver';
|
import * as Receiver from '../../receiver/receiver';
|
||||||
|
@ -12,9 +12,10 @@ import {
|
||||||
updateLastHash,
|
updateLastHash,
|
||||||
} from '../../../ts/data/data';
|
} from '../../../ts/data/data';
|
||||||
|
|
||||||
import { StringUtils } from '../../session/utils';
|
import { StringUtils, UserUtils } from '../../session/utils';
|
||||||
import { getConversationController } from '../conversations';
|
|
||||||
import { ConversationModel } from '../../models/conversation';
|
import { ConversationModel } from '../../models/conversation';
|
||||||
|
import { DURATION, SWARM_POLLING_TIMEOUT } from '../constants';
|
||||||
|
import { getConversationController } from '../conversations';
|
||||||
|
|
||||||
type PubkeyToHash = { [key: string]: string };
|
type PubkeyToHash = { [key: string]: string };
|
||||||
|
|
||||||
|
@ -50,32 +51,37 @@ export const getSwarmPollingInstance = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export class SwarmPolling {
|
export class SwarmPolling {
|
||||||
private pubkeys: Array<PubKey>;
|
private ourPubkey: PubKey | undefined;
|
||||||
private groupPubkeys: Array<PubKey>;
|
private groupPolling: Array<{ pubkey: PubKey; lastPolledTimestamp: number }>;
|
||||||
private readonly lastHashes: { [key: string]: PubkeyToHash };
|
private readonly lastHashes: { [key: string]: PubkeyToHash };
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.pubkeys = [];
|
this.groupPolling = [];
|
||||||
this.groupPubkeys = [];
|
|
||||||
this.lastHashes = {};
|
this.lastHashes = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
public start(): void {
|
public async start(waitForFirstPoll = false): Promise<void> {
|
||||||
|
this.ourPubkey = UserUtils.getOurPubKeyFromCache();
|
||||||
this.loadGroupIds();
|
this.loadGroupIds();
|
||||||
void this.pollForAllKeys();
|
if (waitForFirstPoll) {
|
||||||
}
|
await this.TEST_pollForAllKeys();
|
||||||
|
} else {
|
||||||
public addGroupId(pubkey: PubKey) {
|
void this.TEST_pollForAllKeys();
|
||||||
if (this.groupPubkeys.findIndex(m => m.key === pubkey.key) === -1) {
|
|
||||||
window?.log?.info('Swarm addGroupId: adding pubkey to polling', pubkey.key);
|
|
||||||
this.groupPubkeys.push(pubkey);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public addPubkey(pk: PubKey | string) {
|
/**
|
||||||
const pubkey = PubKey.cast(pk);
|
* Used fo testing only
|
||||||
if (this.pubkeys.findIndex(m => m.key === pubkey.key) === -1) {
|
*/
|
||||||
this.pubkeys.push(pubkey);
|
public TEST_reset() {
|
||||||
|
this.ourPubkey = undefined;
|
||||||
|
this.groupPolling = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public addGroupId(pubkey: PubKey) {
|
||||||
|
if (this.groupPolling.findIndex(m => m.pubkey.key === pubkey.key) === -1) {
|
||||||
|
window?.log?.info('Swarm addGroupId: adding pubkey to polling', pubkey.key);
|
||||||
|
this.groupPolling.push({ pubkey, lastPolledTimestamp: 0 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,16 +89,95 @@ export class SwarmPolling {
|
||||||
const pubkey = PubKey.cast(pk);
|
const pubkey = PubKey.cast(pk);
|
||||||
window?.log?.info('Swarm removePubkey: removing pubkey from polling', pubkey.key);
|
window?.log?.info('Swarm removePubkey: removing pubkey from polling', pubkey.key);
|
||||||
|
|
||||||
this.pubkeys = this.pubkeys.filter(key => !pubkey.isEqual(key));
|
if (this.ourPubkey && PubKey.cast(pk).isEqual(this.ourPubkey)) {
|
||||||
this.groupPubkeys = this.groupPubkeys.filter(key => !pubkey.isEqual(key));
|
this.ourPubkey = undefined;
|
||||||
|
}
|
||||||
|
this.groupPolling = this.groupPolling.filter(group => !pubkey.isEqual(group.pubkey));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async pollOnceForKey(pubkey: PubKey, isGroup: boolean) {
|
/**
|
||||||
|
* Only public for testing
|
||||||
|
* As of today, we pull closed group pubkeys as follow:
|
||||||
|
* if activeAt is not set, poll only once per hour
|
||||||
|
* if activeAt is less than an hour old, poll every 5 seconds or so
|
||||||
|
* if activeAt is less than a day old, poll every minutes only.
|
||||||
|
* If activeAt is more than a day old, poll only once per hour
|
||||||
|
*/
|
||||||
|
public TEST_getPollingTimeout(convoId: PubKey) {
|
||||||
|
const convo = getConversationController().get(convoId.key);
|
||||||
|
if (!convo) {
|
||||||
|
return SWARM_POLLING_TIMEOUT.INACTIVE;
|
||||||
|
}
|
||||||
|
const activeAt = convo.get('active_at');
|
||||||
|
if (!activeAt) {
|
||||||
|
return SWARM_POLLING_TIMEOUT.INACTIVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentTimestamp = Date.now();
|
||||||
|
|
||||||
|
// consider that this is an active group if activeAt is less than an hour old
|
||||||
|
if (currentTimestamp - activeAt <= DURATION.HOURS * 1) {
|
||||||
|
return SWARM_POLLING_TIMEOUT.ACTIVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentTimestamp - activeAt <= DURATION.DAYS * 1) {
|
||||||
|
return SWARM_POLLING_TIMEOUT.MEDIUM_ACTIVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SWARM_POLLING_TIMEOUT.INACTIVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only public for testing
|
||||||
|
*/
|
||||||
|
public async TEST_pollForAllKeys() {
|
||||||
|
// we always poll as often as possible for our pubkey
|
||||||
|
const directPromise = this.ourPubkey
|
||||||
|
? this.TEST_pollOnceForKey(this.ourPubkey, false)
|
||||||
|
: Promise.resolve();
|
||||||
|
|
||||||
|
const now = Date.now();
|
||||||
|
const groupPromises = this.groupPolling.map(async group => {
|
||||||
|
const convoPollingTimeout = this.TEST_getPollingTimeout(group.pubkey);
|
||||||
|
|
||||||
|
const diff = now - group.lastPolledTimestamp;
|
||||||
|
|
||||||
|
const loggingId =
|
||||||
|
getConversationController()
|
||||||
|
.get(group.pubkey.key)
|
||||||
|
?.idForLogging() || group.pubkey.key;
|
||||||
|
|
||||||
|
if (diff >= convoPollingTimeout) {
|
||||||
|
(window?.log?.info || console.warn)(
|
||||||
|
`Polling for ${loggingId}; timeout: ${convoPollingTimeout} ; diff: ${diff}`
|
||||||
|
);
|
||||||
|
return this.TEST_pollOnceForKey(group.pubkey, true);
|
||||||
|
}
|
||||||
|
(window?.log?.info || console.warn)(
|
||||||
|
`Not polling for ${loggingId}; timeout: ${convoPollingTimeout} ; diff: ${diff}`
|
||||||
|
);
|
||||||
|
|
||||||
|
return Promise.resolve();
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
await Promise.all(_.concat(directPromise, groupPromises));
|
||||||
|
} catch (e) {
|
||||||
|
(window?.log?.info || console.warn)('pollForAllKeys swallowing exception: ', e);
|
||||||
|
throw e;
|
||||||
|
} finally {
|
||||||
|
setTimeout(this.TEST_pollForAllKeys.bind(this), SWARM_POLLING_TIMEOUT.ACTIVE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only exposed as public for testing
|
||||||
|
*/
|
||||||
|
public async TEST_pollOnceForKey(pubkey: PubKey, isGroup: boolean) {
|
||||||
// NOTE: sometimes pubkey is string, sometimes it is object, so
|
// NOTE: sometimes pubkey is string, sometimes it is object, so
|
||||||
// accept both until this is fixed:
|
// accept both until this is fixed:
|
||||||
const pkStr = pubkey.key;
|
const pkStr = pubkey.key;
|
||||||
|
|
||||||
const snodes = await getSwarmFor(pkStr);
|
const snodes = await snodePool.getSwarmFor(pkStr);
|
||||||
|
|
||||||
// Select nodes for which we already have lastHashes
|
// Select nodes for which we already have lastHashes
|
||||||
const alreadyPolled = snodes.filter((n: Snode) => this.lastHashes[n.pubkey_ed25519]);
|
const alreadyPolled = snodes.filter((n: Snode) => this.lastHashes[n.pubkey_ed25519]);
|
||||||
|
@ -123,6 +208,19 @@ export class SwarmPolling {
|
||||||
// Merge results into one list of unique messages
|
// Merge results into one list of unique messages
|
||||||
const messages = _.uniqBy(_.flatten(results), (x: any) => x.hash);
|
const messages = _.uniqBy(_.flatten(results), (x: any) => x.hash);
|
||||||
|
|
||||||
|
if (isGroup) {
|
||||||
|
// update the last fetched timestamp
|
||||||
|
this.groupPolling = this.groupPolling.map(group => {
|
||||||
|
if (PubKey.isEqual(pubkey, group.pubkey)) {
|
||||||
|
return {
|
||||||
|
...group,
|
||||||
|
lastPolledTimestamp: Date.now(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return group;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const newMessages = await this.handleSeenMessages(messages);
|
const newMessages = await this.handleSeenMessages(messages);
|
||||||
|
|
||||||
newMessages.forEach((m: Message) => {
|
newMessages.forEach((m: Message) => {
|
||||||
|
@ -133,7 +231,7 @@ export class SwarmPolling {
|
||||||
|
|
||||||
// Fetches messages for `pubkey` from `node` potentially updating
|
// Fetches messages for `pubkey` from `node` potentially updating
|
||||||
// the lash hash record
|
// the lash hash record
|
||||||
protected async pollNodeForKey(node: Snode, pubkey: PubKey): Promise<Array<any>> {
|
private async pollNodeForKey(node: Snode, pubkey: PubKey): Promise<Array<any>> {
|
||||||
const edkey = node.pubkey_ed25519;
|
const edkey = node.pubkey_ed25519;
|
||||||
|
|
||||||
const pkStr = pubkey.key;
|
const pkStr = pubkey.key;
|
||||||
|
@ -188,24 +286,6 @@ export class SwarmPolling {
|
||||||
return newMessages;
|
return newMessages;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async pollForAllKeys() {
|
|
||||||
const directPromises = this.pubkeys.map(async pk => {
|
|
||||||
return this.pollOnceForKey(pk, false);
|
|
||||||
});
|
|
||||||
|
|
||||||
const groupPromises = this.groupPubkeys.map(async pk => {
|
|
||||||
return this.pollOnceForKey(pk, true);
|
|
||||||
});
|
|
||||||
try {
|
|
||||||
await Promise.all(_.concat(directPromises, groupPromises));
|
|
||||||
} catch (e) {
|
|
||||||
window?.log?.warn('pollForAllKeys swallowing exception: ', e);
|
|
||||||
throw e;
|
|
||||||
} finally {
|
|
||||||
setTimeout(this.pollForAllKeys.bind(this), 2000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async updateLastHash(
|
private async updateLastHash(
|
||||||
edkey: string,
|
edkey: string,
|
||||||
pubkey: PubKey,
|
pubkey: PubKey,
|
||||||
|
|
|
@ -152,6 +152,10 @@ export class PubKey {
|
||||||
return key.replace(PubKey.PREFIX_GROUP_TEXTSECURE, '');
|
return key.replace(PubKey.PREFIX_GROUP_TEXTSECURE, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static isEqual(comparator1: PubKey | string, comparator2: PubKey | string) {
|
||||||
|
return PubKey.cast(comparator1).isEqual(comparator2);
|
||||||
|
}
|
||||||
|
|
||||||
public isEqual(comparator: PubKey | string) {
|
public isEqual(comparator: PubKey | string) {
|
||||||
return comparator instanceof PubKey
|
return comparator instanceof PubKey
|
||||||
? this.key === comparator.key
|
? this.key === comparator.key
|
||||||
|
|
|
@ -90,7 +90,6 @@ export async function addJob(attachment: any, job: any = {}) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// tslint:disable: function-name
|
|
||||||
async function _tick() {
|
async function _tick() {
|
||||||
await _maybeStartJob();
|
await _maybeStartJob();
|
||||||
timeout = setTimeout(_tick, TICK_INTERVAL);
|
timeout = setTimeout(_tick, TICK_INTERVAL);
|
||||||
|
|
|
@ -33,7 +33,6 @@ const defaultRoomsSlice = createSlice({
|
||||||
},
|
},
|
||||||
updateDefaultRoomsInProgress(state, action) {
|
updateDefaultRoomsInProgress(state, action) {
|
||||||
const inProgress = action.payload as boolean;
|
const inProgress = action.payload as boolean;
|
||||||
window?.log?.info('fetching default rooms inProgress?', action.payload);
|
|
||||||
return { ...state, inProgress };
|
return { ...state, inProgress };
|
||||||
},
|
},
|
||||||
updateDefaultBase64RoomData(state, action: PayloadAction<Base64Update>) {
|
updateDefaultBase64RoomData(state, action: PayloadAction<Base64Update>) {
|
||||||
|
|
307
ts/test/session/unit/swarm_polling/SwarmPolling_test.ts
Normal file
307
ts/test/session/unit/swarm_polling/SwarmPolling_test.ts
Normal file
|
@ -0,0 +1,307 @@
|
||||||
|
// tslint:disable: no-implicit-dependencies max-func-body-length no-unused-expression
|
||||||
|
|
||||||
|
import chai from 'chai';
|
||||||
|
import Sinon, * as sinon from 'sinon';
|
||||||
|
import _, { noop } from 'lodash';
|
||||||
|
import { describe } from 'mocha';
|
||||||
|
|
||||||
|
import chaiAsPromised from 'chai-as-promised';
|
||||||
|
import { TestUtils } from '../../../test-utils';
|
||||||
|
import { UserUtils } from '../../../../session/utils';
|
||||||
|
import { getConversationController } from '../../../../session/conversations';
|
||||||
|
import * as Data from '../../../../../ts/data/data';
|
||||||
|
import { getSwarmPollingInstance, SnodePool } from '../../../../session/snode_api';
|
||||||
|
import { SwarmPolling } from '../../../../session/snode_api/swarmPolling';
|
||||||
|
import { SWARM_POLLING_TIMEOUT } from '../../../../session/constants';
|
||||||
|
import {
|
||||||
|
ConversationCollection,
|
||||||
|
ConversationModel,
|
||||||
|
ConversationTypeEnum,
|
||||||
|
} from '../../../../models/conversation';
|
||||||
|
import { PubKey } from '../../../../session/types';
|
||||||
|
// tslint:disable: chai-vague-errors
|
||||||
|
|
||||||
|
chai.use(chaiAsPromised as any);
|
||||||
|
chai.should();
|
||||||
|
|
||||||
|
const { expect } = chai;
|
||||||
|
|
||||||
|
// tslint:disable-next-line: max-func-body-length
|
||||||
|
describe('SwarmPolling', () => {
|
||||||
|
// Initialize new stubbed cache
|
||||||
|
const sandbox = sinon.createSandbox();
|
||||||
|
const ourPubkey = TestUtils.generateFakePubKey();
|
||||||
|
const ourNumber = ourPubkey.key;
|
||||||
|
|
||||||
|
let pollOnceForKeySpy: Sinon.SinonSpy<any>;
|
||||||
|
|
||||||
|
let swarmPolling: SwarmPolling;
|
||||||
|
|
||||||
|
let clock: Sinon.SinonFakeTimers;
|
||||||
|
beforeEach(async () => {
|
||||||
|
// Utils Stubs
|
||||||
|
sandbox.stub(UserUtils, 'getOurPubKeyStrFromCache').returns(ourNumber);
|
||||||
|
|
||||||
|
sandbox.stub(Data, 'getAllConversations').resolves(new ConversationCollection());
|
||||||
|
sandbox.stub(Data, 'getItemById').resolves();
|
||||||
|
sandbox.stub(Data, 'saveConversation').resolves();
|
||||||
|
sandbox.stub(Data, 'getSwarmNodesForPubkey').resolves();
|
||||||
|
sandbox.stub(SnodePool, 'getSwarmFor').resolves([]);
|
||||||
|
TestUtils.stubWindow('profileImages', { removeImagesNotInArray: noop, hasImage: noop });
|
||||||
|
TestUtils.stubWindow('inboxStore', undefined);
|
||||||
|
const convoController = getConversationController();
|
||||||
|
await convoController.load();
|
||||||
|
getConversationController().getOrCreate(ourPubkey.key, ConversationTypeEnum.PRIVATE);
|
||||||
|
|
||||||
|
swarmPolling = getSwarmPollingInstance();
|
||||||
|
swarmPolling.TEST_reset();
|
||||||
|
pollOnceForKeySpy = sandbox.spy(swarmPolling, 'TEST_pollOnceForKey');
|
||||||
|
|
||||||
|
clock = sinon.useFakeTimers(Date.now());
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
TestUtils.restoreStubs();
|
||||||
|
sandbox.restore();
|
||||||
|
getConversationController().reset();
|
||||||
|
clock.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getPollingTimeout', () => {
|
||||||
|
it('returns INACTIVE for non existing convo', () => {
|
||||||
|
const fakeConvo = TestUtils.generateFakePubKey();
|
||||||
|
|
||||||
|
expect(swarmPolling.TEST_getPollingTimeout(fakeConvo)).to.eq(SWARM_POLLING_TIMEOUT.INACTIVE);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns ACTIVE for convo with less than an hour old activeAt', () => {
|
||||||
|
const convo = getConversationController().getOrCreate(
|
||||||
|
TestUtils.generateFakePubKeyStr(),
|
||||||
|
ConversationTypeEnum.GROUP
|
||||||
|
);
|
||||||
|
convo.set('active_at', Date.now() - 3555);
|
||||||
|
expect(swarmPolling.TEST_getPollingTimeout(PubKey.cast(convo.id))).to.eq(
|
||||||
|
SWARM_POLLING_TIMEOUT.ACTIVE
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns INACTIVE for convo with undefined activeAt', () => {
|
||||||
|
const convo = getConversationController().getOrCreate(
|
||||||
|
TestUtils.generateFakePubKeyStr(),
|
||||||
|
ConversationTypeEnum.GROUP
|
||||||
|
);
|
||||||
|
convo.set('active_at', undefined);
|
||||||
|
expect(swarmPolling.TEST_getPollingTimeout(PubKey.cast(convo.id))).to.eq(
|
||||||
|
SWARM_POLLING_TIMEOUT.INACTIVE
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns MEDIUM_ACTIVE for convo with activeAt of less than a day', () => {
|
||||||
|
const convo = getConversationController().getOrCreate(
|
||||||
|
TestUtils.generateFakePubKeyStr(),
|
||||||
|
ConversationTypeEnum.GROUP
|
||||||
|
);
|
||||||
|
convo.set('active_at', Date.now() - 1000 * 3600 * 23);
|
||||||
|
expect(swarmPolling.TEST_getPollingTimeout(PubKey.cast(convo.id))).to.eq(
|
||||||
|
SWARM_POLLING_TIMEOUT.MEDIUM_ACTIVE
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns INACTIVE for convo with activeAt of more than a day', () => {
|
||||||
|
const convo = getConversationController().getOrCreate(
|
||||||
|
TestUtils.generateFakePubKeyStr(),
|
||||||
|
ConversationTypeEnum.GROUP
|
||||||
|
);
|
||||||
|
convo.set('active_at', Date.now() - 1000 * 3600 * 25);
|
||||||
|
expect(swarmPolling.TEST_getPollingTimeout(PubKey.cast(convo.id))).to.eq(
|
||||||
|
SWARM_POLLING_TIMEOUT.INACTIVE
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('pollForAllKeys', () => {
|
||||||
|
it('does run for our pubkey even if activeAt is really old ', async () => {
|
||||||
|
const convo = getConversationController().getOrCreate(
|
||||||
|
ourNumber,
|
||||||
|
ConversationTypeEnum.PRIVATE
|
||||||
|
);
|
||||||
|
convo.set('active_at', Date.now() - 1000 * 3600 * 25);
|
||||||
|
await swarmPolling.start(true);
|
||||||
|
|
||||||
|
expect(pollOnceForKeySpy.callCount).to.eq(1);
|
||||||
|
expect(pollOnceForKeySpy.firstCall.args).to.deep.eq([ourPubkey, false]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does run for our pubkey even if activeAt is recent ', async () => {
|
||||||
|
const convo = getConversationController().getOrCreate(
|
||||||
|
ourNumber,
|
||||||
|
ConversationTypeEnum.PRIVATE
|
||||||
|
);
|
||||||
|
convo.set('active_at', Date.now());
|
||||||
|
await swarmPolling.start(true);
|
||||||
|
|
||||||
|
expect(pollOnceForKeySpy.callCount).to.eq(1);
|
||||||
|
expect(pollOnceForKeySpy.firstCall.args).to.deep.eq([ourPubkey, false]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does run for group pubkey on start no matter the recent timestamp ', async () => {
|
||||||
|
const convo = getConversationController().getOrCreate(
|
||||||
|
TestUtils.generateFakePubKeyStr(),
|
||||||
|
ConversationTypeEnum.GROUP
|
||||||
|
);
|
||||||
|
convo.set('active_at', Date.now());
|
||||||
|
const groupConvoPubkey = PubKey.cast(convo.id);
|
||||||
|
swarmPolling.addGroupId(groupConvoPubkey);
|
||||||
|
await swarmPolling.start(true);
|
||||||
|
|
||||||
|
// our pubkey will be polled for, hence the 2
|
||||||
|
expect(pollOnceForKeySpy.callCount).to.eq(2);
|
||||||
|
expect(pollOnceForKeySpy.firstCall.args).to.deep.eq([ourPubkey, false]);
|
||||||
|
expect(pollOnceForKeySpy.secondCall.args).to.deep.eq([groupConvoPubkey, true]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does run for group pubkey on start no matter the old timestamp ', async () => {
|
||||||
|
const convo = getConversationController().getOrCreate(
|
||||||
|
TestUtils.generateFakePubKeyStr(),
|
||||||
|
ConversationTypeEnum.GROUP
|
||||||
|
);
|
||||||
|
|
||||||
|
convo.set('active_at', 1);
|
||||||
|
const groupConvoPubkey = PubKey.cast(convo.id);
|
||||||
|
swarmPolling.addGroupId(groupConvoPubkey);
|
||||||
|
await swarmPolling.start(true);
|
||||||
|
|
||||||
|
// our pubkey will be polled for, hence the 2
|
||||||
|
expect(pollOnceForKeySpy.callCount).to.eq(2);
|
||||||
|
expect(pollOnceForKeySpy.firstCall.args).to.deep.eq([ourPubkey, false]);
|
||||||
|
expect(pollOnceForKeySpy.secondCall.args).to.deep.eq([groupConvoPubkey, true]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does run for group pubkey on start but not another time if activeAt is old ', async () => {
|
||||||
|
const convo = getConversationController().getOrCreate(
|
||||||
|
TestUtils.generateFakePubKeyStr(),
|
||||||
|
ConversationTypeEnum.GROUP
|
||||||
|
);
|
||||||
|
|
||||||
|
convo.set('active_at', 1);
|
||||||
|
const groupConvoPubkey = PubKey.cast(convo.id);
|
||||||
|
swarmPolling.addGroupId(groupConvoPubkey);
|
||||||
|
await swarmPolling.start(true);
|
||||||
|
|
||||||
|
await swarmPolling.TEST_pollForAllKeys();
|
||||||
|
|
||||||
|
expect(pollOnceForKeySpy.callCount).to.eq(3);
|
||||||
|
expect(pollOnceForKeySpy.secondCall.args).to.deep.eq([groupConvoPubkey, true]);
|
||||||
|
expect(pollOnceForKeySpy.thirdCall.args).to.deep.eq([ourPubkey, false]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does run twice if activeAt less than one hour ', async () => {
|
||||||
|
const convo = getConversationController().getOrCreate(
|
||||||
|
TestUtils.generateFakePubKeyStr(),
|
||||||
|
ConversationTypeEnum.GROUP
|
||||||
|
);
|
||||||
|
|
||||||
|
convo.set('active_at', Date.now());
|
||||||
|
const groupConvoPubkey = PubKey.cast(convo.id);
|
||||||
|
swarmPolling.addGroupId(groupConvoPubkey);
|
||||||
|
await swarmPolling.start(true);
|
||||||
|
clock.tick(6000);
|
||||||
|
// no need to do that as the tick will trigger a call in all cases after 5 secs
|
||||||
|
// await swarmPolling.TEST_pollForAllKeys();
|
||||||
|
|
||||||
|
expect(pollOnceForKeySpy.callCount).to.eq(4);
|
||||||
|
expect(pollOnceForKeySpy.secondCall.args).to.deep.eq([groupConvoPubkey, true]);
|
||||||
|
expect(pollOnceForKeySpy.thirdCall.args).to.deep.eq([ourPubkey, false]);
|
||||||
|
expect(pollOnceForKeySpy.lastCall.args).to.deep.eq([groupConvoPubkey, true]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does run once only if activeAt is more than one hour', async () => {
|
||||||
|
const convo = getConversationController().getOrCreate(
|
||||||
|
TestUtils.generateFakePubKeyStr(),
|
||||||
|
ConversationTypeEnum.GROUP
|
||||||
|
);
|
||||||
|
|
||||||
|
convo.set('active_at', Date.now());
|
||||||
|
const groupConvoPubkey = PubKey.cast(convo.id);
|
||||||
|
swarmPolling.addGroupId(groupConvoPubkey);
|
||||||
|
await swarmPolling.start(true);
|
||||||
|
|
||||||
|
// more than hour old, we should not tick after just 5 seconds
|
||||||
|
convo.set('active_at', Date.now() - 3605 * 1000);
|
||||||
|
|
||||||
|
clock.tick(6000);
|
||||||
|
|
||||||
|
expect(pollOnceForKeySpy.callCount).to.eq(3);
|
||||||
|
expect(pollOnceForKeySpy.secondCall.args).to.deep.eq([groupConvoPubkey, true]);
|
||||||
|
expect(pollOnceForKeySpy.thirdCall.args).to.deep.eq([ourPubkey, false]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does run once if activeAt is more than 1 days old ', async () => {
|
||||||
|
const convo = getConversationController().getOrCreate(
|
||||||
|
TestUtils.generateFakePubKeyStr(),
|
||||||
|
ConversationTypeEnum.GROUP
|
||||||
|
);
|
||||||
|
|
||||||
|
convo.set('active_at', Date.now());
|
||||||
|
const groupConvoPubkey = PubKey.cast(convo.id);
|
||||||
|
swarmPolling.addGroupId(groupConvoPubkey);
|
||||||
|
await swarmPolling.start(true);
|
||||||
|
|
||||||
|
// more than hour old, we should not tick after just 5 seconds
|
||||||
|
convo.set('active_at', Date.now() - 25 * 3600 * 1000);
|
||||||
|
|
||||||
|
clock.tick(6 * 1000); // active
|
||||||
|
|
||||||
|
expect(pollOnceForKeySpy.callCount).to.eq(3);
|
||||||
|
expect(pollOnceForKeySpy.secondCall.args).to.deep.eq([groupConvoPubkey, true]);
|
||||||
|
expect(pollOnceForKeySpy.thirdCall.args).to.deep.eq([ourPubkey, false]);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('multiple runs', () => {
|
||||||
|
let convo: ConversationModel;
|
||||||
|
let groupConvoPubkey: PubKey;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
convo = getConversationController().getOrCreate(
|
||||||
|
TestUtils.generateFakePubKeyStr(),
|
||||||
|
ConversationTypeEnum.GROUP
|
||||||
|
);
|
||||||
|
|
||||||
|
convo.set('active_at', Date.now());
|
||||||
|
groupConvoPubkey = PubKey.cast(convo.id);
|
||||||
|
swarmPolling.addGroupId(groupConvoPubkey);
|
||||||
|
await swarmPolling.start(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does run twice if activeAt is more than 1 hour old and we tick more than one minute ', async () => {
|
||||||
|
pollOnceForKeySpy.resetHistory();
|
||||||
|
// more than hour old but less than a day, we should tick after just 60 seconds
|
||||||
|
convo.set('active_at', Date.now() - 3605 * 1000);
|
||||||
|
|
||||||
|
clock.tick(61 * 1000); // medium_active
|
||||||
|
|
||||||
|
await swarmPolling.TEST_pollForAllKeys();
|
||||||
|
expect(pollOnceForKeySpy.callCount).to.eq(3);
|
||||||
|
// first two calls are our pubkey
|
||||||
|
expect(pollOnceForKeySpy.firstCall.args).to.deep.eq([ourPubkey, false]);
|
||||||
|
expect(pollOnceForKeySpy.secondCall.args).to.deep.eq([ourPubkey, false]);
|
||||||
|
|
||||||
|
expect(pollOnceForKeySpy.thirdCall.args).to.deep.eq([groupConvoPubkey, true]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does run twice if activeAt is more than 1 day old and we tick more than one hour ', async () => {
|
||||||
|
pollOnceForKeySpy.resetHistory();
|
||||||
|
convo.set('active_at', Date.now() - 25 * 3600 * 1000);
|
||||||
|
|
||||||
|
clock.tick(3700 * 1000); // inactive
|
||||||
|
|
||||||
|
await swarmPolling.TEST_pollForAllKeys();
|
||||||
|
expect(pollOnceForKeySpy.callCount).to.eq(3);
|
||||||
|
// first two calls are our pubkey
|
||||||
|
expect(pollOnceForKeySpy.firstCall.args).to.deep.eq([ourPubkey, false]);
|
||||||
|
expect(pollOnceForKeySpy.secondCall.args).to.deep.eq([ourPubkey, false]);
|
||||||
|
expect(pollOnceForKeySpy.thirdCall.args).to.deep.eq([groupConvoPubkey, true]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -11,6 +11,15 @@ export function generateFakePubKey(): PubKey {
|
||||||
return new PubKey(pubkeyString);
|
return new PubKey(pubkeyString);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function generateFakePubKeyStr(): string {
|
||||||
|
// Generates a mock pubkey for testing
|
||||||
|
const numBytes = PubKey.PUBKEY_LEN / 2 - 1;
|
||||||
|
const hexBuffer = crypto.randomBytes(numBytes).toString('hex');
|
||||||
|
const pubkeyString = `05${hexBuffer}`;
|
||||||
|
|
||||||
|
return pubkeyString;
|
||||||
|
}
|
||||||
|
|
||||||
export function generateFakeECKeyPair(): ECKeyPair {
|
export function generateFakeECKeyPair(): ECKeyPair {
|
||||||
const pubkey = generateFakePubKey().toArray();
|
const pubkey = generateFakePubKey().toArray();
|
||||||
const privKey = new Uint8Array(crypto.randomBytes(64));
|
const privKey = new Uint8Array(crypto.randomBytes(64));
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { createOrUpdateItem, getItemById } from '../../ts/data/data';
|
import { createOrUpdateItem, getItemById } from '../data/data';
|
||||||
import { PubKey } from '../session/types';
|
import { PubKey } from '../session/types';
|
||||||
import { UserUtils } from '../session/utils';
|
import { UserUtils } from '../session/utils';
|
||||||
|
|
||||||
|
|
|
@ -65,10 +65,10 @@
|
||||||
"function-name": [
|
"function-name": [
|
||||||
true,
|
true,
|
||||||
{
|
{
|
||||||
"function-regex": "^[a-z][\\w\\d]+$",
|
"function-regex": "^(TEST_)?(_)?[a-z][\\w\\d]+$",
|
||||||
"method-regex": "^[a-z][\\w\\d]+$",
|
"method-regex": "^(TEST_)?(_)?[a-z][\\w\\d]+$",
|
||||||
"private-method-regex": "^[a-z][\\w\\d]+$",
|
"private-method-regex": "^(TEST_)?(_)?[a-z][\\w\\d]+$",
|
||||||
"protected-method-regex": "^[a-z][\\w\\d]+$",
|
"protected-method-regex": "^(TEST_)?(_)?[a-z][\\w\\d]+$",
|
||||||
"static-method-regex": "^[a-zA-Z][\\w\\d]+$"
|
"static-method-regex": "^[a-zA-Z][\\w\\d]+$"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
Loading…
Reference in a new issue