diff --git a/libtextsecure/test/_test.js b/libtextsecure/test/_test.js index 72a59c975..a5c008261 100644 --- a/libtextsecure/test/_test.js +++ b/libtextsecure/test/_test.js @@ -77,4 +77,6 @@ before(async () => { err && err.stack ? err.stack : err ); } + + await window.Signal.Util.initializeMessageCounter(); }); diff --git a/test/_test.js b/test/_test.js index 74b180a1e..9ccca4066 100644 --- a/test/_test.js +++ b/test/_test.js @@ -88,6 +88,7 @@ before(async () => { err && err.stack ? err.stack : err ); } + await window.Signal.Util.initializeMessageCounter(); await window.Signal.Data.removeAll(); await window.storage.fetch(); }); diff --git a/ts/background.ts b/ts/background.ts index 2d63b2713..ffa2c7fc2 100644 --- a/ts/background.ts +++ b/ts/background.ts @@ -139,6 +139,8 @@ export async function startApp(): Promise { ); } + await window.Signal.Util.initializeMessageCounter(); + // Initialize WebAPI as early as possible let server: WebAPIType | undefined; let messageReceiver: MessageReceiver | undefined; diff --git a/ts/sql/Client.ts b/ts/sql/Client.ts index b0e2f60fe..1df621b7a 100644 --- a/ts/sql/Client.ts +++ b/ts/sql/Client.ts @@ -277,6 +277,8 @@ const dataInterface: ClientInterface = { processGroupCallRingCancelation, cleanExpiredGroupCallRings, + getMaxMessageCounter, + getStatisticsForLogging, // Test-only @@ -1656,6 +1658,10 @@ async function updateAllConversationColors( ); } +function getMaxMessageCounter(): Promise { + return channels.getMaxMessageCounter(); +} + function getStatisticsForLogging(): Promise> { return channels.getStatisticsForLogging(); } diff --git a/ts/sql/Interface.ts b/ts/sql/Interface.ts index 3fa9dffe4..720a07e03 100644 --- a/ts/sql/Interface.ts +++ b/ts/sql/Interface.ts @@ -486,6 +486,7 @@ export type DataInterface = { } ) => Promise; + getMaxMessageCounter(): Promise; getStatisticsForLogging(): Promise>; }; diff --git a/ts/sql/Server.ts b/ts/sql/Server.ts index cd1cfa774..4ec35beeb 100644 --- a/ts/sql/Server.ts +++ b/ts/sql/Server.ts @@ -268,6 +268,8 @@ const dataInterface: ServerInterface = { processGroupCallRingCancelation, cleanExpiredGroupCallRings, + getMaxMessageCounter, + getStatisticsForLogging, // Server-only @@ -6493,6 +6495,25 @@ async function cleanExpiredGroupCallRings(): Promise { }); } +async function getMaxMessageCounter(): Promise { + const db = getInstance(); + + return db + .prepare( + ` + SELECT MAX(counter) + FROM + ( + SELECT MAX(received_at) AS counter FROM messages + UNION + SELECT MAX(timestamp) AS counter FROM unprocessed + ) + ` + ) + .pluck() + .get(); +} + async function getStatisticsForLogging(): Promise> { const counts = await pProps({ messageCount: getMessageCount(), diff --git a/ts/util/incrementMessageCounter.ts b/ts/util/incrementMessageCounter.ts index 98c668d41..f393cd1ff 100644 --- a/ts/util/incrementMessageCounter.ts +++ b/ts/util/incrementMessageCounter.ts @@ -1,16 +1,57 @@ // Copyright 2021 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import { debounce } from 'lodash'; +import { debounce, isNumber } from 'lodash'; + +import { strictAssert } from './assert'; +import Data from '../sql/Client'; let receivedAtCounter: number | undefined; -export function incrementMessageCounter(): number { - if (!receivedAtCounter) { - receivedAtCounter = - Number(localStorage.getItem('lastReceivedAtCounter')) || Date.now(); +export async function initializeMessageCounter(): Promise { + strictAssert( + receivedAtCounter === undefined, + 'incrementMessageCounter: already initialized' + ); + + const storedCounter = Number(localStorage.getItem('lastReceivedAtCounter')); + const dbCounter = await Data.getMaxMessageCounter(); + + if (isNumber(dbCounter) && isNumber(storedCounter)) { + window.log.info( + 'initializeMessageCounter: picking max of db/stored counters' + ); + receivedAtCounter = Math.max(dbCounter, storedCounter); + + if (receivedAtCounter !== storedCounter) { + window.log.warn( + 'initializeMessageCounter: mismatch between db/stored counters' + ); + } + } else if (isNumber(storedCounter)) { + window.log.info('initializeMessageCounter: picking stored counter'); + receivedAtCounter = storedCounter; + } else if (isNumber(dbCounter)) { + window.log.info( + 'initializeMessageCounter: picking fallback counter from the database' + ); + receivedAtCounter = dbCounter; + } else { + window.log.info('initializeMessageCounter: defaulting to Date.now()'); + receivedAtCounter = Date.now(); } + if (storedCounter !== receivedAtCounter) { + localStorage.setItem('lastReceivedAtCounter', String(receivedAtCounter)); + } +} + +export function incrementMessageCounter(): number { + strictAssert( + receivedAtCounter !== undefined, + 'incrementMessageCounter: not initialized' + ); + receivedAtCounter += 1; debouncedUpdateLastReceivedAt(); diff --git a/ts/util/index.ts b/ts/util/index.ts index 9742ca5e3..f2b23dcc0 100644 --- a/ts/util/index.ts +++ b/ts/util/index.ts @@ -15,6 +15,7 @@ import { getTextWithMentions } from './getTextWithMentions'; import { getUserAgent } from './getUserAgent'; import { hasExpired } from './hasExpired'; import { + initializeMessageCounter, incrementMessageCounter, flushMessageCounter, } from './incrementMessageCounter'; @@ -61,6 +62,7 @@ export { getUserAgent, hasExpired, incrementMessageCounter, + initializeMessageCounter, isFileDangerous, longRunningTaskWrapper, makeLookup,