add polling from namespace 0 and storing lastHash with namespace

pull/2290/head
Audric Ackermann 3 years ago
parent 50ca1bcda9
commit 363977b358
No known key found for this signature in database
GPG Key ID: 999F434D76324AD4

@ -15,7 +15,7 @@ import { getConversationController } from '../session/conversations';
import { getSodiumRenderer } from '../session/crypto';
import { PubKey } from '../session/types';
import { ReduxConversationType } from '../state/ducks/conversations';
import { MsgDuplicateSearchOpenGroup } from '../types/sqlSharedTypes';
import { MsgDuplicateSearchOpenGroup, UpdateLastHashType } from '../types/sqlSharedTypes';
import { ExpirationTimerOptions } from '../util/expiringMessages';
import { Storage } from '../util/storage';
import { channels } from './channels';
@ -303,12 +303,7 @@ export async function saveSeenMessageHashes(
await channels.saveSeenMessageHashes(_cleanData(data));
}
export async function updateLastHash(data: {
convoId: string;
snode: string;
hash: string;
expiresAt: number;
}): Promise<void> {
export async function updateLastHash(data: UpdateLastHashType): Promise<void> {
await channels.updateLastHash(_cleanData(data));
}
@ -519,8 +514,12 @@ export async function getFirstUnreadMessageWithMention(
export async function hasConversationOutgoingMessage(conversationId: string): Promise<boolean> {
return channels.hasConversationOutgoingMessage(conversationId);
}
export async function getLastHashBySnode(convoId: string, snode: string): Promise<string> {
return channels.getLastHashBySnode(convoId, snode);
export async function getLastHashBySnode(
convoId: string,
snode: string,
namespace: number
): Promise<string> {
return channels.getLastHashBySnode(convoId, snode, namespace);
}
export async function getSeenMessagesByHashList(hashes: Array<string>): Promise<any> {

@ -23,6 +23,7 @@ import { LocaleMessagesType } from './locale'; // checked - only node
import { PubKey } from '../session/types/PubKey'; // checked - only node
import { StorageItem } from './storage_item'; // checked - only node
import { getAppRootPath } from './getRootPath';
import { UpdateLastHashType } from '../types/sqlSharedTypes';
// tslint:disable: no-console quotemark non-literal-fs-path one-variable-per-declaration
const openDbOptions = {
// tslint:disable-next-line: no-constant-condition
@ -47,6 +48,7 @@ const GUARD_NODE_TABLE = 'guardNodes';
const ITEMS_TABLE = 'items';
const ATTACHMENT_DOWNLOADS_TABLE = 'attachment_downloads';
const CLOSED_GROUP_V2_KEY_PAIRS_TABLE = 'encryptionKeyPairsForClosedGroupV2';
const LAST_HASHES_TABLE = 'lastHashes';
const MAX_PUBKEYS_MEMBERS = 300;
@ -434,7 +436,7 @@ function updateToSchemaVersion6(currentVersion: number, db: BetterSqlite3.Databa
console.log('updateToSchemaVersion6: starting...');
db.transaction(() => {
db.exec(`
CREATE TABLE lastHashes(
CREATE TABLE ${LAST_HASHES_TABLE}(
snode TEXT PRIMARY KEY,
hash TEXT,
expiresAt INTEGER
@ -766,6 +768,7 @@ const LOKI_SCHEMA_VERSIONS = [
updateToLokiSchemaVersion20,
updateToLokiSchemaVersion21,
updateToLokiSchemaVersion22,
updateToLokiSchemaVersion23,
];
function updateToLokiSchemaVersion1(currentVersion: number, db: BetterSqlite3.Database) {
@ -846,8 +849,8 @@ function updateToLokiSchemaVersion4(currentVersion: number, db: BetterSqlite3.Da
db.transaction(() => {
db.exec(`
DROP TABLE lastHashes;
CREATE TABLE lastHashes(
DROP TABLE ${LAST_HASHES_TABLE};
CREATE TABLE ${LAST_HASHES_TABLE}(
id TEXT,
snode TEXT,
hash TEXT,
@ -1359,6 +1362,40 @@ function updateToLokiSchemaVersion22(currentVersion: number, db: BetterSqlite3.D
console.log(`updateToLokiSchemaVersion${targetVersion}: success!`);
}
function updateToLokiSchemaVersion23(currentVersion: number, db: BetterSqlite3.Database) {
const targetVersion = 23;
if (currentVersion >= targetVersion) {
return;
}
console.log(`updateToLokiSchemaVersion${targetVersion}: starting...`);
db.transaction(() => {
db.exec(
`
ALTER TABLE ${LAST_HASHES_TABLE} RENAME TO ${LAST_HASHES_TABLE}_old;
CREATE TABLE ${LAST_HASHES_TABLE}(
id TEXT,
snode TEXT,
hash TEXT,
expiresAt INTEGER,
namespace INTEGER NOT NULL DEFAULT 0,
PRIMARY KEY (id, snode, namespace)
);
`
);
db.exec(
`INSERT INTO ${LAST_HASHES_TABLE}(id, snode, hash, expiresAt) SELECT id, snode, hash, expiresAt FROM ${LAST_HASHES_TABLE}_old;`
);
db.exec(`DROP TABLE ${LAST_HASHES_TABLE}_old;`);
writeLokiSchemaVersion(targetVersion, db);
})();
console.log(`updateToLokiSchemaVersion${targetVersion}: success!`);
}
function writeLokiSchemaVersion(newVersion: number, db: BetterSqlite3.Database) {
db.prepare(
`INSERT INTO loki_schema(
@ -2111,30 +2148,33 @@ function saveSeenMessageHashes(arrayOfHashes: Array<string>) {
})();
}
function updateLastHash(data: any) {
const { convoId, snode, hash, expiresAt } = data;
const id = convoId;
function updateLastHash(data: UpdateLastHashType) {
const { convoId, snode, hash, expiresAt, namespace } = data;
if (!isNumber(namespace)) {
throw new Error('updateLastHash: namespace must be set to a number');
}
assertGlobalInstance()
.prepare(
`INSERT OR REPLACE INTO lastHashes (
`INSERT OR REPLACE INTO ${LAST_HASHES_TABLE} (
id,
snode,
hash,
expiresAt
expiresAt,
namespace
) values (
$id,
$snode,
$hash,
$expiresAt
$expiresAt,
$namespace
)`
)
.run({
id,
id: convoId,
snode,
hash,
expiresAt,
namespace,
});
}
@ -2162,7 +2202,7 @@ function saveSeenMessageHash(data: any) {
function cleanLastHashes() {
assertGlobalInstance()
.prepare('DELETE FROM lastHashes WHERE expiresAt <= $now;')
.prepare(`DELETE FROM ${LAST_HASHES_TABLE} WHERE expiresAt <= $now;`)
.run({
now: Date.now(),
});
@ -2579,12 +2619,18 @@ function getMessagesBySentAt(sentAt: number) {
return map(rows, row => jsonToObject(row.json));
}
function getLastHashBySnode(convoId: string, snode: string) {
function getLastHashBySnode(convoId: string, snode: string, namespace: number) {
if (!isNumber(namespace)) {
throw new Error('getLastHashBySnode: namespace must be set to a number');
}
const row = assertGlobalInstance()
.prepare('SELECT * FROM lastHashes WHERE snode = $snode AND id = $id;')
.prepare(
`SELECT * FROM ${LAST_HASHES_TABLE} WHERE snode = $snode AND id = $id AND namespace = $namespace;`
)
.get({
snode,
id: convoId,
namespace,
});
if (!row) {
@ -2848,7 +2894,7 @@ function removeAll() {
DELETE FROM ${ITEMS_TABLE};
DELETE FROM unprocessed;
DELETE FROM lastHashes;
DELETE FROM ${LAST_HASHES_TABLE};
DELETE FROM ${NODES_FOR_PUBKEY_TABLE};
DELETE FROM ${CLOSED_GROUP_V2_KEY_PAIRS_TABLE};
DELETE FROM seenMessages;

@ -505,7 +505,7 @@ export async function retrieveNextMessages(
targetNode: Snode,
lastHash: string,
associatedWith: string,
namespace?: number
namespace: number
): Promise<Array<any>> {
const params: RetrieveRequestParams = {
pubKey: associatedWith,

@ -21,8 +21,6 @@ import { ed25519Str } from '../../onions/onionPath';
import { updateIsOnline } from '../../../state/ducks/onion';
import pRetry from 'p-retry';
type PubkeyToHash = { [key: string]: string };
interface Message {
hash: string;
expiration: number;
@ -56,7 +54,7 @@ export const getSwarmPollingInstance = () => {
export class SwarmPolling {
private groupPolling: Array<{ pubkey: PubKey; lastPolledTimestamp: number }>;
private readonly lastHashes: { [key: string]: PubkeyToHash };
private readonly lastHashes: Record<string, Record<string, Record<number, string>>>;
constructor() {
this.groupPolling = [];
@ -66,20 +64,20 @@ export class SwarmPolling {
public async start(waitForFirstPoll = false): Promise<void> {
this.loadGroupIds();
if (waitForFirstPoll) {
await this.TEST_pollForAllKeys();
await this.pollForAllKeys();
} else {
void this.TEST_pollForAllKeys();
void this.pollForAllKeys();
}
}
/**
* Used fo testing only
*/
public TEST_reset() {
public resetSwarmPolling() {
this.groupPolling = [];
}
public TEST_forcePolledTimestamp(pubkey: PubKey, lastPoll: number) {
public forcePolledTimestamp(pubkey: PubKey, lastPoll: number) {
this.groupPolling = this.groupPolling.map(group => {
if (PubKey.isEqual(pubkey, group.pubkey)) {
return {
@ -112,7 +110,7 @@ export class SwarmPolling {
* -> an activeAt less than 1 week old is considered medium_active and polled a bit less (every minute)
* -> an activeAt more than a week old is considered inactive, and not polled much (every 2 minutes)
*/
public TEST_getPollingTimeout(convoId: PubKey) {
public getPollingTimeout(convoId: PubKey) {
const convo = getConversationController().get(convoId.key);
if (!convo) {
return SWARM_POLLING_TIMEOUT.INACTIVE;
@ -138,20 +136,20 @@ export class SwarmPolling {
/**
* Only public for testing
*/
public async TEST_pollForAllKeys() {
public async pollForAllKeys() {
if (!window.getGlobalOnlineStatus()) {
window?.log?.error('pollForAllKeys: offline');
// Important to set up a new polling
setTimeout(this.TEST_pollForAllKeys.bind(this), SWARM_POLLING_TIMEOUT.ACTIVE);
setTimeout(this.pollForAllKeys.bind(this), SWARM_POLLING_TIMEOUT.ACTIVE);
return;
}
// we always poll as often as possible for our pubkey
const ourPubkey = UserUtils.getOurPubKeyFromCache();
const directPromise = this.TEST_pollOnceForKey(ourPubkey, false);
const directPromise = this.pollOnceForKey(ourPubkey, false, 0);
const now = Date.now();
const groupPromises = this.groupPolling.map(async group => {
const convoPollingTimeout = this.TEST_getPollingTimeout(group.pubkey);
const convoPollingTimeout = this.getPollingTimeout(group.pubkey);
const diff = now - group.lastPolledTimestamp;
@ -164,7 +162,7 @@ export class SwarmPolling {
window?.log?.info(
`Polling for ${loggingId}; timeout: ${convoPollingTimeout} ; diff: ${diff}`
);
return this.TEST_pollOnceForKey(group.pubkey, true);
return this.pollOnceForKey(group.pubkey, true, 0);
}
window?.log?.info(
`Not polling for ${loggingId}; timeout: ${convoPollingTimeout} ; diff: ${diff}`
@ -178,22 +176,20 @@ export class SwarmPolling {
window?.log?.info('pollForAllKeys exception: ', e);
throw e;
} finally {
setTimeout(this.TEST_pollForAllKeys.bind(this), SWARM_POLLING_TIMEOUT.ACTIVE);
setTimeout(this.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
// accept both until this is fixed:
public async pollOnceForKey(pubkey: PubKey, isGroup: boolean, namespace: number) {
const pkStr = pubkey.key;
const snodes = await snodePool.getSwarmFor(pkStr);
const swarmSnodes = await snodePool.getSwarmFor(pkStr);
// Select nodes for which we already have lastHashes
const alreadyPolled = snodes.filter((n: Snode) => this.lastHashes[n.pubkey_ed25519]);
const alreadyPolled = swarmSnodes.filter((n: Snode) => this.lastHashes[n.pubkey_ed25519]);
// If we need more nodes, select randomly from the remaining nodes:
@ -202,7 +198,7 @@ export class SwarmPolling {
let nodesToPoll = _.sampleSize(alreadyPolled, COUNT);
if (nodesToPoll.length < COUNT) {
const notPolled = _.difference(snodes, alreadyPolled);
const notPolled = _.difference(swarmSnodes, alreadyPolled);
const newNeeded = COUNT - alreadyPolled.length;
@ -213,7 +209,7 @@ export class SwarmPolling {
const promisesSettled = await Promise.allSettled(
nodesToPoll.map(async (n: Snode) => {
return this.pollNodeForKey(n, pubkey);
return this.pollNodeForKey(n, pubkey, namespace);
})
);
@ -271,24 +267,33 @@ export class SwarmPolling {
// Fetches messages for `pubkey` from `node` potentially updating
// the lash hash record
private async pollNodeForKey(node: Snode, pubkey: PubKey): Promise<Array<any> | null> {
private async pollNodeForKey(
node: Snode,
pubkey: PubKey,
namespace: number
): Promise<Array<any> | null> {
const edkey = node.pubkey_ed25519;
const pkStr = pubkey.key;
const prevHash = await this.getLastHash(edkey, pkStr);
try {
return await pRetry(
async () => {
const messages = await retrieveNextMessages(node, prevHash, pkStr);
const prevHash = await this.getLastHash(edkey, pkStr, namespace);
const messages = await retrieveNextMessages(node, prevHash, pkStr, namespace);
if (!messages.length) {
return [];
}
const lastMessage = _.last(messages);
await this.updateLastHash(edkey, pubkey, lastMessage.hash, lastMessage.expiration);
await this.updateLastHash({
edkey: edkey,
pubkey,
namespace,
hash: lastMessage.hash,
expiration: lastMessage.expiration,
});
return messages;
},
{
@ -350,12 +355,19 @@ export class SwarmPolling {
return newMessages;
}
private async updateLastHash(
edkey: string,
pubkey: PubKey,
hash: string,
expiration: number
): Promise<void> {
private async updateLastHash({
edkey,
expiration,
hash,
namespace,
pubkey,
}: {
edkey: string;
pubkey: PubKey;
namespace: number;
hash: string;
expiration: number;
}): Promise<void> {
const pkStr = pubkey.key;
await updateLastHash({
@ -363,27 +375,33 @@ export class SwarmPolling {
snode: edkey,
hash,
expiresAt: expiration,
namespace,
});
if (!this.lastHashes[edkey]) {
this.lastHashes[edkey] = {};
}
this.lastHashes[edkey][pkStr] = hash;
if (!this.lastHashes[edkey][pkStr]) {
this.lastHashes[edkey][pkStr] = {};
}
this.lastHashes[edkey][pkStr][namespace] = hash;
}
private async getLastHash(nodeEdKey: string, pubkey: string): Promise<string> {
// TODO: always retrieve from the database?
const nodeRecords = this.lastHashes[nodeEdKey];
private async getLastHash(nodeEdKey: string, pubkey: string, namespace: number): Promise<string> {
if (!this.lastHashes[nodeEdKey]?.[pubkey]?.[namespace]) {
const lastHash = await getLastHashBySnode(pubkey, nodeEdKey, namespace);
if (!nodeRecords || !nodeRecords[pubkey]) {
const lastHash = await getLastHashBySnode(pubkey, nodeEdKey);
if (!this.lastHashes[nodeEdKey]) {
this.lastHashes[nodeEdKey] = {};
}
return lastHash || '';
} else {
// Don't need to go to the database every time:
return nodeRecords[pubkey];
if (!this.lastHashes[nodeEdKey][pubkey]) {
this.lastHashes[nodeEdKey][pubkey] = {};
}
this.lastHashes[nodeEdKey][pubkey][namespace] = lastHash || '';
return this.lastHashes[nodeEdKey][pubkey][namespace];
}
// return the cached value
return this.lastHashes[nodeEdKey][pubkey][namespace];
}
}

@ -34,7 +34,7 @@ describe('SwarmPolling', () => {
const ourNumber = ourPubkey.key;
// tslint:disable-next-line: variable-name
let TEST_pollOnceForKeySpy: Sinon.SinonSpy<any>;
let pollOnceForKeySpy: Sinon.SinonSpy<any>;
let swarmPolling: SwarmPolling;
@ -60,8 +60,8 @@ describe('SwarmPolling', () => {
getConversationController().getOrCreate(ourPubkey.key, ConversationTypeEnum.PRIVATE);
swarmPolling = getSwarmPollingInstance();
swarmPolling.TEST_reset();
TEST_pollOnceForKeySpy = Sinon.spy(swarmPolling, 'TEST_pollOnceForKey');
swarmPolling.resetSwarmPolling();
pollOnceForKeySpy = Sinon.spy(swarmPolling, 'pollOnceForKey');
clock = sinon.useFakeTimers(Date.now());
});
@ -76,7 +76,7 @@ describe('SwarmPolling', () => {
it('returns INACTIVE for non existing convo', () => {
const fakeConvo = TestUtils.generateFakePubKey();
expect(swarmPolling.TEST_getPollingTimeout(fakeConvo)).to.eq(SWARM_POLLING_TIMEOUT.INACTIVE);
expect(swarmPolling.getPollingTimeout(fakeConvo)).to.eq(SWARM_POLLING_TIMEOUT.INACTIVE);
});
it('returns ACTIVE for convo with less than two days old activeAt', () => {
@ -85,7 +85,7 @@ describe('SwarmPolling', () => {
ConversationTypeEnum.GROUP
);
convo.set('active_at', Date.now() - 2 * 23 * 3600 * 1000); // 23 * 2 = 46 hours old
expect(swarmPolling.TEST_getPollingTimeout(PubKey.cast(convo.id as string))).to.eq(
expect(swarmPolling.getPollingTimeout(PubKey.cast(convo.id as string))).to.eq(
SWARM_POLLING_TIMEOUT.ACTIVE
);
});
@ -96,7 +96,7 @@ describe('SwarmPolling', () => {
ConversationTypeEnum.GROUP
);
convo.set('active_at', undefined);
expect(swarmPolling.TEST_getPollingTimeout(PubKey.cast(convo.id as string))).to.eq(
expect(swarmPolling.getPollingTimeout(PubKey.cast(convo.id as string))).to.eq(
SWARM_POLLING_TIMEOUT.INACTIVE
);
});
@ -107,12 +107,12 @@ describe('SwarmPolling', () => {
ConversationTypeEnum.GROUP
);
convo.set('active_at', Date.now() - 1000 * 3600 * 25 * 2); // 25 hours x 2 = 50 hours old
expect(swarmPolling.TEST_getPollingTimeout(PubKey.cast(convo.id as string))).to.eq(
expect(swarmPolling.getPollingTimeout(PubKey.cast(convo.id as string))).to.eq(
SWARM_POLLING_TIMEOUT.MEDIUM_ACTIVE
);
convo.set('active_at', Date.now() - 1000 * 3600 * 24 * 7 + 3600); // a week minus an hour old
expect(swarmPolling.TEST_getPollingTimeout(PubKey.cast(convo.id as string))).to.eq(
expect(swarmPolling.getPollingTimeout(PubKey.cast(convo.id as string))).to.eq(
SWARM_POLLING_TIMEOUT.MEDIUM_ACTIVE
);
});
@ -123,7 +123,7 @@ describe('SwarmPolling', () => {
ConversationTypeEnum.GROUP
);
convo.set('active_at', Date.now() - 1000 * 3600 * 24 * 8); // 8 days
expect(swarmPolling.TEST_getPollingTimeout(PubKey.cast(convo.id as string))).to.eq(
expect(swarmPolling.getPollingTimeout(PubKey.cast(convo.id as string))).to.eq(
SWARM_POLLING_TIMEOUT.INACTIVE
);
});
@ -138,8 +138,8 @@ describe('SwarmPolling', () => {
convo.set('active_at', Date.now() - 1000 * 3600 * 25);
await swarmPolling.start(true);
expect(TEST_pollOnceForKeySpy.callCount).to.eq(1);
expect(TEST_pollOnceForKeySpy.firstCall.args).to.deep.eq([ourPubkey, false]);
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 () => {
@ -150,8 +150,8 @@ describe('SwarmPolling', () => {
convo.set('active_at', Date.now());
await swarmPolling.start(true);
expect(TEST_pollOnceForKeySpy.callCount).to.eq(1);
expect(TEST_pollOnceForKeySpy.firstCall.args).to.deep.eq([ourPubkey, false]);
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 () => {
@ -165,9 +165,9 @@ describe('SwarmPolling', () => {
await swarmPolling.start(true);
// our pubkey will be polled for, hence the 2
expect(TEST_pollOnceForKeySpy.callCount).to.eq(2);
expect(TEST_pollOnceForKeySpy.firstCall.args).to.deep.eq([ourPubkey, false]);
expect(TEST_pollOnceForKeySpy.secondCall.args).to.deep.eq([groupConvoPubkey, true]);
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 () => {
@ -182,9 +182,9 @@ describe('SwarmPolling', () => {
await swarmPolling.start(true);
// our pubkey will be polled for, hence the 2
expect(TEST_pollOnceForKeySpy.callCount).to.eq(2);
expect(TEST_pollOnceForKeySpy.firstCall.args).to.deep.eq([ourPubkey, false]);
expect(TEST_pollOnceForKeySpy.secondCall.args).to.deep.eq([groupConvoPubkey, true]);
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 () => {
@ -201,12 +201,12 @@ describe('SwarmPolling', () => {
await swarmPolling.start(true);
// this should only call the stub one more time: for our direct pubkey but not for the group pubkey
await swarmPolling.TEST_pollForAllKeys();
await swarmPolling.pollForAllKeys();
expect(TEST_pollOnceForKeySpy.callCount).to.eq(3);
expect(TEST_pollOnceForKeySpy.firstCall.args).to.deep.eq([ourPubkey, false]);
expect(TEST_pollOnceForKeySpy.secondCall.args).to.deep.eq([groupConvoPubkey, true]);
expect(TEST_pollOnceForKeySpy.thirdCall.args).to.deep.eq([ourPubkey, false]);
expect(pollOnceForKeySpy.callCount).to.eq(3);
expect(pollOnceForKeySpy.firstCall.args).to.deep.eq([ourPubkey, false]);
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 () => {
@ -221,12 +221,12 @@ describe('SwarmPolling', () => {
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();
// await swarmPolling.pollForAllKeys();
expect(TEST_pollOnceForKeySpy.callCount).to.eq(4);
expect(TEST_pollOnceForKeySpy.secondCall.args).to.deep.eq([groupConvoPubkey, true]);
expect(TEST_pollOnceForKeySpy.thirdCall.args).to.deep.eq([ourPubkey, false]);
expect(TEST_pollOnceForKeySpy.lastCall.args).to.deep.eq([groupConvoPubkey, true]);
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 twice if activeAt is inactive and we tick longer than 2 minutes', async () => {
@ -235,24 +235,24 @@ describe('SwarmPolling', () => {
ConversationTypeEnum.GROUP
);
TEST_pollOnceForKeySpy.resetHistory();
pollOnceForKeySpy.resetHistory();
convo.set('active_at', Date.now());
const groupConvoPubkey = PubKey.cast(convo.id as string);
swarmPolling.addGroupId(groupConvoPubkey);
// this call the stub two times already, one for our direct pubkey and one for the group
await swarmPolling.start(true);
const timeToTick = 3 * 60 * 1000;
swarmPolling.TEST_forcePolledTimestamp(groupConvoPubkey, Date.now() - timeToTick);
swarmPolling.forcePolledTimestamp(groupConvoPubkey, Date.now() - timeToTick);
// more than week old, so inactive group but we have to tick after more than 2 min
convo.set('active_at', Date.now() - 7 * 25 * 3600 * 1000);
clock.tick(timeToTick);
// we should have two more calls here, so 4 total.
expect(TEST_pollOnceForKeySpy.callCount).to.eq(4);
expect(TEST_pollOnceForKeySpy.firstCall.args).to.deep.eq([ourPubkey, false]);
expect(TEST_pollOnceForKeySpy.secondCall.args).to.deep.eq([groupConvoPubkey, true]);
expect(TEST_pollOnceForKeySpy.thirdCall.args).to.deep.eq([ourPubkey, false]);
expect(TEST_pollOnceForKeySpy.getCalls()[3].args).to.deep.eq([groupConvoPubkey, true]);
expect(pollOnceForKeySpy.callCount).to.eq(4);
expect(pollOnceForKeySpy.firstCall.args).to.deep.eq([ourPubkey, false]);
expect(pollOnceForKeySpy.secondCall.args).to.deep.eq([groupConvoPubkey, true]);
expect(pollOnceForKeySpy.thirdCall.args).to.deep.eq([ourPubkey, false]);
expect(pollOnceForKeySpy.getCalls()[3].args).to.deep.eq([groupConvoPubkey, true]);
});
it('does run once only if group is inactive and we tick less than 2 minutes ', async () => {
@ -260,7 +260,7 @@ describe('SwarmPolling', () => {
TestUtils.generateFakePubKeyStr(),
ConversationTypeEnum.GROUP
);
TEST_pollOnceForKeySpy.resetHistory();
pollOnceForKeySpy.resetHistory();
convo.set('active_at', Date.now());
const groupConvoPubkey = PubKey.cast(convo.id as string);
@ -273,9 +273,9 @@ describe('SwarmPolling', () => {
clock.tick(1 * 60 * 1000);
// we should have only one more call here, the one for our direct pubkey fetch
expect(TEST_pollOnceForKeySpy.callCount).to.eq(3);
expect(TEST_pollOnceForKeySpy.secondCall.args).to.deep.eq([groupConvoPubkey, true]); // this one comes from the swarmPolling.start
expect(TEST_pollOnceForKeySpy.thirdCall.args).to.deep.eq([ourPubkey, false]);
expect(pollOnceForKeySpy.callCount).to.eq(3);
expect(pollOnceForKeySpy.secondCall.args).to.deep.eq([groupConvoPubkey, true]); // this one comes from the swarmPolling.start
expect(pollOnceForKeySpy.thirdCall.args).to.deep.eq([ourPubkey, false]);
});
describe('multiple runs', () => {
@ -295,45 +295,45 @@ describe('SwarmPolling', () => {
});
it('does run twice if activeAt is less than 2 days', async () => {
TEST_pollOnceForKeySpy.resetHistory();
pollOnceForKeySpy.resetHistory();
// less than 2 days old, this is an active group
convo.set('active_at', Date.now() - 2 * 24 * 3600 * 1000 - 3600 * 1000);
const timeToTick = 6 * 1000;
swarmPolling.TEST_forcePolledTimestamp(convo.id, timeToTick);
swarmPolling.forcePolledTimestamp(convo.id, timeToTick);
// we tick more than 5 sec
clock.tick(timeToTick);
await swarmPolling.TEST_pollForAllKeys();
await swarmPolling.pollForAllKeys();
// we have 4 calls total. 2 for our direct promises run each 5 seconds, and 2 for the group pubkey active (so run every 5 sec too)
expect(TEST_pollOnceForKeySpy.callCount).to.eq(4);
expect(pollOnceForKeySpy.callCount).to.eq(4);
// first two calls are our pubkey
expect(TEST_pollOnceForKeySpy.firstCall.args).to.deep.eq([ourPubkey, false]);
expect(TEST_pollOnceForKeySpy.secondCall.args).to.deep.eq([groupConvoPubkey, true]);
expect(pollOnceForKeySpy.firstCall.args).to.deep.eq([ourPubkey, false]);
expect(pollOnceForKeySpy.secondCall.args).to.deep.eq([groupConvoPubkey, true]);
expect(TEST_pollOnceForKeySpy.thirdCall.args).to.deep.eq([ourPubkey, false]);
expect(TEST_pollOnceForKeySpy.getCalls()[3].args).to.deep.eq([groupConvoPubkey, true]);
expect(pollOnceForKeySpy.thirdCall.args).to.deep.eq([ourPubkey, false]);
expect(pollOnceForKeySpy.getCalls()[3].args).to.deep.eq([groupConvoPubkey, true]);
});
it('does run twice if activeAt is more than 2 days old and we tick more than one minute ', async () => {
TEST_pollOnceForKeySpy.resetHistory();
pollOnceForKeySpy.resetHistory();
convo.set('active_at', Date.now() - 2 * 25 * 3600 * 1000); // medium active
const timeToTick = 65 * 1000;
swarmPolling.TEST_forcePolledTimestamp(convo.id, timeToTick);
swarmPolling.forcePolledTimestamp(convo.id, timeToTick);
clock.tick(timeToTick); // should tick twice more (one more our direct pubkey and one for the group)
await swarmPolling.TEST_pollForAllKeys();
expect(TEST_pollOnceForKeySpy.callCount).to.eq(4);
await swarmPolling.pollForAllKeys();
expect(pollOnceForKeySpy.callCount).to.eq(4);
// first two calls are our pubkey
expect(TEST_pollOnceForKeySpy.firstCall.args).to.deep.eq([ourPubkey, false]);
expect(TEST_pollOnceForKeySpy.secondCall.args).to.deep.eq([groupConvoPubkey, true]);
expect(pollOnceForKeySpy.firstCall.args).to.deep.eq([ourPubkey, false]);
expect(pollOnceForKeySpy.secondCall.args).to.deep.eq([groupConvoPubkey, true]);
expect(TEST_pollOnceForKeySpy.thirdCall.args).to.deep.eq([ourPubkey, false]);
expect(TEST_pollOnceForKeySpy.getCalls()[3].args).to.deep.eq([groupConvoPubkey, true]);
expect(pollOnceForKeySpy.thirdCall.args).to.deep.eq([ourPubkey, false]);
expect(pollOnceForKeySpy.getCalls()[3].args).to.deep.eq([groupConvoPubkey, true]);
});
});
});

@ -2,3 +2,11 @@ export type MsgDuplicateSearchOpenGroup = Array<{
sender: string;
serverTimestamp: number;
}>;
export type UpdateLastHashType = {
convoId: string;
snode: string;
hash: string;
expiresAt: number;
namespace: number;
};

Loading…
Cancel
Save