diff --git a/ts/LibSignalStore.ts b/ts/LibSignalStore.ts index eb34e1be2b6..69355add264 100644 --- a/ts/LibSignalStore.ts +++ b/ts/LibSignalStore.ts @@ -5,6 +5,7 @@ import { fromEncodedBinaryToArrayBuffer, constantTimeEqual } from './Crypto'; import { isNotNil } from './util/isNotNil'; +import { isMoreRecentThan } from './util/timestamp'; const TIMESTAMP_THRESHOLD = 5 * 1000; // 5 seconds const Direction = { @@ -839,7 +840,7 @@ export class SignalProtocolStore extends EventsMixin { isNonBlockingApprovalRequired(identityRecord: IdentityKeyType): boolean { return ( !identityRecord.firstUse && - Date.now() - identityRecord.timestamp < TIMESTAMP_THRESHOLD && + isMoreRecentThan(identityRecord.timestamp, TIMESTAMP_THRESHOLD) && !identityRecord.nonblockingApproval ); } @@ -1138,7 +1139,7 @@ export class SignalProtocolStore extends EventsMixin { } if ( - Date.now() - identityRecord.timestamp < TIMESTAMP_THRESHOLD && + isMoreRecentThan(identityRecord.timestamp, TIMESTAMP_THRESHOLD) && !identityRecord.nonblockingApproval && !identityRecord.firstUse ) { diff --git a/ts/background.ts b/ts/background.ts index 55ae986ec00..eb9e45dd3ff 100644 --- a/ts/background.ts +++ b/ts/background.ts @@ -8,6 +8,9 @@ import { getTitleBarVisibility, TitleBarVisibility } from './types/Settings'; import { isWindowDragElement } from './util/isWindowDragElement'; import { assert } from './util/assert'; import { routineProfileRefresh } from './routineProfileRefresh'; +import { isMoreRecentThan, isOlderThan } from './util/timestamp'; + +const MAX_ATTACHMENT_DOWNLOAD_AGE = 3600 * 72 * 1000; export async function startApp(): Promise { window.startupProcessingQueue = new window.Signal.Util.StartupQueue(); @@ -555,12 +558,11 @@ export async function startApp(): Promise { }; // How long since we were last running? - const now = Date.now(); const lastHeartbeat = window.storage.get('lastHeartbeat'); await window.storage.put('lastStartup', Date.now()); const THIRTY_DAYS = 30 * 24 * 60 * 60 * 1000; - if (lastHeartbeat > 0 && now - lastHeartbeat > THIRTY_DAYS) { + if (lastHeartbeat > 0 && isOlderThan(lastHeartbeat, THIRTY_DAYS)) { await unlinkAndDisconnect(); } @@ -2097,12 +2099,14 @@ export async function startApp(): Promise { // once we stop processing the queue. window.attachmentDownloadQueue = undefined; - const THREE_DAYS_AGO = Date.now() - 3600 * 72 * 1000; const MAX_ATTACHMENT_MSGS_TO_DOWNLOAD = 250; const attachmentsToDownload = attachmentDownloadQueue.filter( (message, index) => index <= MAX_ATTACHMENT_MSGS_TO_DOWNLOAD || - message.getReceivedAt() > THREE_DAYS_AGO || + isMoreRecentThan( + message.getReceivedAt(), + MAX_ATTACHMENT_DOWNLOAD_AGE + ) || // Stickers and long text attachments has to be downloaded for UI // to display the message properly. message.hasRequiredAttachmentDownloads() diff --git a/ts/routineProfileRefresh.ts b/ts/routineProfileRefresh.ts index dea6cea2091..aa5329ecc81 100644 --- a/ts/routineProfileRefresh.ts +++ b/ts/routineProfileRefresh.ts @@ -8,12 +8,14 @@ import { assert } from './util/assert'; import { missingCaseError } from './util/missingCaseError'; import { isNormalNumber } from './util/isNormalNumber'; import { map, take } from './util/iterables'; +import { isOlderThan } from './util/timestamp'; import { ConversationModel } from './models/conversations'; const STORAGE_KEY = 'lastAttemptedToRefreshProfilesAt'; const MAX_AGE_TO_BE_CONSIDERED_ACTIVE = 30 * 24 * 60 * 60 * 1000; const MAX_AGE_TO_BE_CONSIDERED_RECENTLY_REFRESHED = 1 * 24 * 60 * 60 * 1000; const MAX_CONVERSATIONS_TO_REFRESH = 50; +const MIN_ELAPSED_DURATION_TO_REFRESH_AGAIN = 12 * 3600 * 1000; // This type is a little stricter than what's on `window.storage`, and only requires what // we need for easier testing. @@ -81,8 +83,7 @@ function hasEnoughTimeElapsedSinceLastRefresh(storage: StorageType): boolean { } if (isNormalNumber(storedValue)) { - const twelveHoursAgo = Date.now() - 43200000; - return storedValue < twelveHoursAgo; + return isOlderThan(storedValue, MIN_ELAPSED_DURATION_TO_REFRESH_AGAIN); } assert( diff --git a/ts/test-both/util/timestamp_test.ts b/ts/test-both/util/timestamp_test.ts new file mode 100644 index 00000000000..7ff94a41e9d --- /dev/null +++ b/ts/test-both/util/timestamp_test.ts @@ -0,0 +1,37 @@ +// Copyright 2021 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import { assert } from 'chai'; + +import { isOlderThan, isMoreRecentThan } from '../../util/timestamp'; + +const ONE_HOUR = 3600 * 1000; +const ONE_DAY = 24 * ONE_HOUR; + +describe('timestamp', () => { + describe('isOlderThan', () => { + it('returns false on recent and future timestamps', () => { + assert.isFalse(isOlderThan(Date.now(), ONE_DAY)); + assert.isFalse(isOlderThan(Date.now() + ONE_DAY, ONE_DAY)); + }); + + it('returns true on old enough timestamps', () => { + assert.isFalse(isOlderThan(Date.now() - ONE_DAY + ONE_HOUR, ONE_DAY)); + assert.isTrue(isOlderThan(Date.now() - ONE_DAY - ONE_HOUR, ONE_DAY)); + }); + }); + + describe('isMoreRecentThan', () => { + it('returns true on recent and future timestamps', () => { + assert.isTrue(isMoreRecentThan(Date.now(), ONE_DAY)); + assert.isTrue(isMoreRecentThan(Date.now() + ONE_DAY, ONE_DAY)); + }); + + it('returns false on old enough timestamps', () => { + assert.isTrue(isMoreRecentThan(Date.now() - ONE_DAY + ONE_HOUR, ONE_DAY)); + assert.isFalse( + isMoreRecentThan(Date.now() - ONE_DAY - ONE_HOUR, ONE_DAY) + ); + }); + }); +}); diff --git a/ts/textsecure/AccountManager.ts b/ts/textsecure/AccountManager.ts index befb493dd38..5e80cdbeaa3 100644 --- a/ts/textsecure/AccountManager.ts +++ b/ts/textsecure/AccountManager.ts @@ -17,8 +17,10 @@ import ProvisioningCipher from './ProvisioningCipher'; import WebSocketResource, { IncomingWebSocketRequest, } from './WebsocketResources'; +import { isMoreRecentThan, isOlderThan } from '../util/timestamp'; const ARCHIVE_AGE = 30 * 24 * 60 * 60 * 1000; +const PREKEY_ROTATION_AGE = 24 * 60 * 60 * 1000; function getIdentifier(id: string) { if (!id || !id.length) { @@ -321,10 +323,9 @@ export default class AccountManager extends EventTarget { existingKeys.sort((a, b) => (b.created_at || 0) - (a.created_at || 0)); const confirmedKeys = existingKeys.filter(key => key.confirmed); - const ONE_DAY_AGO = Date.now() - 24 * 60 * 60 * 1000; if ( confirmedKeys.length >= 3 && - confirmedKeys[0].created_at > ONE_DAY_AGO + isMoreRecentThan(confirmedKeys[0].created_at, PREKEY_ROTATION_AGE) ) { window.log.warn( 'rotateSignedPreKey: 3+ confirmed keys, most recent is less than a day old. Cancelling rotation.' @@ -437,9 +438,8 @@ export default class AccountManager extends EventTarget { return; } const createdAt = key.created_at || 0; - const age = Date.now() - createdAt; - if (age > ARCHIVE_AGE) { + if (isOlderThan(createdAt, ARCHIVE_AGE)) { window.log.info( 'Removing confirmed signed prekey:', key.keyId, @@ -463,8 +463,7 @@ export default class AccountManager extends EventTarget { } const createdAt = key.created_at || 0; - const age = Date.now() - createdAt; - if (age > ARCHIVE_AGE) { + if (isOlderThan(createdAt, ARCHIVE_AGE)) { window.log.info( 'Removing unconfirmed signed prekey:', key.keyId, diff --git a/ts/util/isConversationUnregistered.ts b/ts/util/isConversationUnregistered.ts index 5a324b7f125..f8a66063e5b 100644 --- a/ts/util/isConversationUnregistered.ts +++ b/ts/util/isConversationUnregistered.ts @@ -1,6 +1,8 @@ // Copyright 2021 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only +import { isMoreRecentThan } from './timestamp'; + const SIX_HOURS = 1000 * 60 * 60 * 6; export function isConversationUnregistered({ @@ -8,6 +10,6 @@ export function isConversationUnregistered({ }: Readonly<{ discoveredUnregisteredAt?: number }>): boolean { return Boolean( discoveredUnregisteredAt && - discoveredUnregisteredAt > Date.now() - SIX_HOURS + isMoreRecentThan(discoveredUnregisteredAt, SIX_HOURS) ); } diff --git a/ts/util/lint/exceptions.json b/ts/util/lint/exceptions.json index d48e7e29cdf..cc5d4012b37 100644 --- a/ts/util/lint/exceptions.json +++ b/ts/util/lint/exceptions.json @@ -14026,7 +14026,7 @@ "rule": "jQuery-load(", "path": "ts/LibSignalStore.js", "line": " await window.ConversationController.load();", - "lineNumber": 810, + "lineNumber": 811, "reasonCategory": "falseMatch", "updated": "2021-02-27T00:48:49.313Z" }, @@ -14034,7 +14034,7 @@ "rule": "jQuery-load(", "path": "ts/LibSignalStore.ts", "line": " await window.ConversationController.load();", - "lineNumber": 1221, + "lineNumber": 1222, "reasonCategory": "falseMatch", "updated": "2021-02-27T00:48:49.313Z" }, diff --git a/ts/util/timestamp.ts b/ts/util/timestamp.ts new file mode 100644 index 00000000000..d2f924017fd --- /dev/null +++ b/ts/util/timestamp.ts @@ -0,0 +1,10 @@ +// Copyright 2021 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +export function isMoreRecentThan(timestamp: number, delta: number): boolean { + return timestamp > Date.now() - delta; +} + +export function isOlderThan(timestamp: number, delta: number): boolean { + return timestamp <= Date.now() - delta; +}