From 363977b358105dcc820dfb2034176bfb8d69fc34 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Wed, 4 May 2022 14:54:38 +1000 Subject: [PATCH] add polling from namespace 0 and storing lastHash with namespace --- ts/data/data.ts | 17 ++- ts/node/sql.ts | 78 +++++++++--- ts/session/apis/snode_api/SNodeAPI.ts | 2 +- ts/session/apis/snode_api/swarmPolling.ts | 106 ++++++++++------- .../unit/swarm_polling/SwarmPolling_test.ts | 112 +++++++++--------- ts/types/sqlSharedTypes.ts | 8 ++ 6 files changed, 197 insertions(+), 126 deletions(-) diff --git a/ts/data/data.ts b/ts/data/data.ts index 8f5056de9..2191d1279 100644 --- a/ts/data/data.ts +++ b/ts/data/data.ts @@ -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 { +export async function updateLastHash(data: UpdateLastHashType): Promise { await channels.updateLastHash(_cleanData(data)); } @@ -519,8 +514,12 @@ export async function getFirstUnreadMessageWithMention( export async function hasConversationOutgoingMessage(conversationId: string): Promise { return channels.hasConversationOutgoingMessage(conversationId); } -export async function getLastHashBySnode(convoId: string, snode: string): Promise { - return channels.getLastHashBySnode(convoId, snode); +export async function getLastHashBySnode( + convoId: string, + snode: string, + namespace: number +): Promise { + return channels.getLastHashBySnode(convoId, snode, namespace); } export async function getSeenMessagesByHashList(hashes: Array): Promise { diff --git a/ts/node/sql.ts b/ts/node/sql.ts index cfce612a2..8b35fd747 100644 --- a/ts/node/sql.ts +++ b/ts/node/sql.ts @@ -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) { })(); } -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; diff --git a/ts/session/apis/snode_api/SNodeAPI.ts b/ts/session/apis/snode_api/SNodeAPI.ts index 30868c66c..0ee5f9604 100644 --- a/ts/session/apis/snode_api/SNodeAPI.ts +++ b/ts/session/apis/snode_api/SNodeAPI.ts @@ -505,7 +505,7 @@ export async function retrieveNextMessages( targetNode: Snode, lastHash: string, associatedWith: string, - namespace?: number + namespace: number ): Promise> { const params: RetrieveRequestParams = { pubKey: associatedWith, diff --git a/ts/session/apis/snode_api/swarmPolling.ts b/ts/session/apis/snode_api/swarmPolling.ts index fa3033b55..b6cb04d14 100644 --- a/ts/session/apis/snode_api/swarmPolling.ts +++ b/ts/session/apis/snode_api/swarmPolling.ts @@ -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>>; constructor() { this.groupPolling = []; @@ -66,20 +64,20 @@ export class SwarmPolling { public async start(waitForFirstPoll = false): Promise { 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 | null> { + private async pollNodeForKey( + node: Snode, + pubkey: PubKey, + namespace: number + ): Promise | 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 { + private async updateLastHash({ + edkey, + expiration, + hash, + namespace, + pubkey, + }: { + edkey: string; + pubkey: PubKey; + namespace: number; + hash: string; + expiration: number; + }): Promise { 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 { - // TODO: always retrieve from the database? - - const nodeRecords = this.lastHashes[nodeEdKey]; + private async getLastHash(nodeEdKey: string, pubkey: string, namespace: number): Promise { + 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]; } } diff --git a/ts/test/session/unit/swarm_polling/SwarmPolling_test.ts b/ts/test/session/unit/swarm_polling/SwarmPolling_test.ts index a15c2a5a8..5b010717d 100644 --- a/ts/test/session/unit/swarm_polling/SwarmPolling_test.ts +++ b/ts/test/session/unit/swarm_polling/SwarmPolling_test.ts @@ -34,7 +34,7 @@ describe('SwarmPolling', () => { const ourNumber = ourPubkey.key; // tslint:disable-next-line: variable-name - let TEST_pollOnceForKeySpy: Sinon.SinonSpy; + let pollOnceForKeySpy: Sinon.SinonSpy; 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]); }); }); }); diff --git a/ts/types/sqlSharedTypes.ts b/ts/types/sqlSharedTypes.ts index f4b90dacc..557164cf4 100644 --- a/ts/types/sqlSharedTypes.ts +++ b/ts/types/sqlSharedTypes.ts @@ -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; +};