Support for global.messageQueueTimeInSeconds
Co-authored-by: Fedor Indutny <79877362+indutny-signal@users.noreply.github.com>
This commit is contained in:
parent
23e3a847d1
commit
6cc07a4d17
11 changed files with 57 additions and 31 deletions
|
@ -36,6 +36,7 @@ export type ConfigKeyType =
|
|||
| 'global.calling.maxGroupCallRingSize'
|
||||
| 'global.groupsv2.groupSizeHardLimit'
|
||||
| 'global.groupsv2.maxGroupSize'
|
||||
| 'global.messageQueueTimeInSeconds'
|
||||
| 'global.nicknames.max'
|
||||
| 'global.nicknames.min'
|
||||
| 'global.textAttachmentLimitBytes';
|
||||
|
|
|
@ -44,6 +44,7 @@ import { storageJobQueue } from '../util/JobQueue';
|
|||
import { sleep } from '../util/sleep';
|
||||
import { isMoreRecentThan, isOlderThan } from '../util/timestamp';
|
||||
import { map, filter } from '../util/iterables';
|
||||
import { getMessageQueueTime } from '../util/getMessageQueueTime';
|
||||
import { ourProfileKeyService } from './ourProfileKey';
|
||||
import {
|
||||
ConversationTypes,
|
||||
|
@ -350,7 +351,10 @@ async function generateManifest(
|
|||
|
||||
if (
|
||||
storyDistributionList.deletedAtTimestamp != null &&
|
||||
isOlderThan(storyDistributionList.deletedAtTimestamp, durations.MONTH)
|
||||
isOlderThan(
|
||||
storyDistributionList.deletedAtTimestamp,
|
||||
getMessageQueueTime()
|
||||
)
|
||||
) {
|
||||
const droppedID = storyDistributionList.storageID;
|
||||
const droppedVersion = storyDistributionList.storageVersion;
|
||||
|
@ -1316,7 +1320,7 @@ async function processManifest(
|
|||
'unregistered and not in remote manifest'
|
||||
);
|
||||
conversation.setUnregistered({
|
||||
timestamp: Date.now() - durations.MONTH,
|
||||
timestamp: Date.now() - getMessageQueueTime(),
|
||||
fromStorageService: true,
|
||||
|
||||
// Saving below
|
||||
|
|
|
@ -72,11 +72,9 @@ import {
|
|||
fromRootKeyBytes,
|
||||
getRoomIdFromRootKey,
|
||||
} from '../util/callLinksRingrtc';
|
||||
import {
|
||||
CALL_LINK_DELETED_STORAGE_RECORD_TTL,
|
||||
fromAdminKeyBytes,
|
||||
} from '../util/callLinks';
|
||||
import { fromAdminKeyBytes } from '../util/callLinks';
|
||||
import { isOlderThan } from '../util/timestamp';
|
||||
import { getMessageQueueTime } from '../util/getMessageQueueTime';
|
||||
import { callLinkRefreshJobQueue } from '../jobs/callLinkRefreshJobQueue';
|
||||
|
||||
const MY_STORY_BYTES = uuidToBytes(MY_STORY_ID);
|
||||
|
@ -1981,8 +1979,7 @@ export async function mergeCallLinkRecord(
|
|||
? getTimestampFromLong(callLinkRecord.deletedAtTimestampMs)
|
||||
: null;
|
||||
const shouldDrop =
|
||||
deletedAt != null &&
|
||||
isOlderThan(deletedAt, CALL_LINK_DELETED_STORAGE_RECORD_TTL);
|
||||
deletedAt != null && isOlderThan(deletedAt, getMessageQueueTime());
|
||||
if (shouldDrop) {
|
||||
details.push('expired deleted call link; scheduling for removal');
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
import { debounce } from 'lodash';
|
||||
import { DataReader } from '../sql/Client';
|
||||
import { clearTimeoutIfNecessary } from '../util/clearTimeoutIfNecessary';
|
||||
import { DAY } from '../util/durations';
|
||||
import { getMessageQueueTime } from '../util/getMessageQueueTime';
|
||||
import * as Errors from '../types/errors';
|
||||
|
||||
async function eraseTapToViewMessages() {
|
||||
|
@ -12,7 +12,9 @@ async function eraseTapToViewMessages() {
|
|||
window.SignalContext.log.info(
|
||||
'eraseTapToViewMessages: Loading messages...'
|
||||
);
|
||||
const messages = await DataReader.getTapToViewMessagesNeedingErase();
|
||||
const maxTimestamp = Date.now() - getMessageQueueTime();
|
||||
const messages =
|
||||
await DataReader.getTapToViewMessagesNeedingErase(maxTimestamp);
|
||||
await Promise.all(
|
||||
messages.map(async fromDB => {
|
||||
const message = window.MessageCache.__DEPRECATED$register(
|
||||
|
@ -59,7 +61,7 @@ class TapToViewMessagesDeletionService {
|
|||
return;
|
||||
}
|
||||
|
||||
const nextCheck = receivedAt + 30 * DAY;
|
||||
const nextCheck = receivedAt + getMessageQueueTime();
|
||||
window.SignalContext.log.info(
|
||||
'checkTapToViewMessages: next check at',
|
||||
new Date(nextCheck).toISOString()
|
||||
|
|
|
@ -541,7 +541,9 @@ type ReadableInterface = {
|
|||
getMessagesUnexpectedlyMissingExpirationStartTimestamp: () => Array<MessageType>;
|
||||
getSoonestMessageExpiry: () => undefined | number;
|
||||
getNextTapToViewMessageTimestampToAgeOut: () => undefined | number;
|
||||
getTapToViewMessagesNeedingErase: () => Array<MessageType>;
|
||||
getTapToViewMessagesNeedingErase: (
|
||||
maxTimestamp: number
|
||||
) => Array<MessageType>;
|
||||
// getOlderMessagesByConversation is JSON on server, full message on Client
|
||||
getAllStories: (options: {
|
||||
conversationId?: string;
|
||||
|
|
|
@ -4524,9 +4524,10 @@ function getNextTapToViewMessageTimestampToAgeOut(
|
|||
return isNormalNumber(result) ? result : undefined;
|
||||
}
|
||||
|
||||
function getTapToViewMessagesNeedingErase(db: ReadableDB): Array<MessageType> {
|
||||
const THIRTY_DAYS_AGO = Date.now() - 30 * 24 * 60 * 60 * 1000;
|
||||
|
||||
function getTapToViewMessagesNeedingErase(
|
||||
db: ReadableDB,
|
||||
maxTimestamp: number
|
||||
): Array<MessageType> {
|
||||
const rows: JSONRows = db
|
||||
.prepare<Query>(
|
||||
`
|
||||
|
@ -4535,12 +4536,12 @@ function getTapToViewMessagesNeedingErase(db: ReadableDB): Array<MessageType> {
|
|||
WHERE
|
||||
isViewOnce = 1
|
||||
AND (isErased IS NULL OR isErased != 1)
|
||||
AND received_at <= $THIRTY_DAYS_AGO
|
||||
AND received_at <= $maxTimestamp
|
||||
ORDER BY received_at ASC, sent_at ASC;
|
||||
`
|
||||
)
|
||||
.all({
|
||||
THIRTY_DAYS_AGO,
|
||||
maxTimestamp,
|
||||
});
|
||||
|
||||
return rows.map(row => jsonToObject(row.json));
|
||||
|
|
|
@ -59,6 +59,7 @@ import * as log from '../logging/log';
|
|||
import type { StorageAccessType } from '../types/Storage';
|
||||
import { getRelativePath, createName } from '../util/attachmentPath';
|
||||
import { isBackupEnabled } from '../util/isBackupEnabled';
|
||||
import { getMessageQueueTime } from '../util/getMessageQueueTime';
|
||||
|
||||
type StorageKeyByServiceIdKind = {
|
||||
[kind in ServiceIdKind]: keyof StorageAccessType;
|
||||
|
@ -77,7 +78,6 @@ export const KYBER_KEY_ID_KEY: StorageKeyByServiceIdKind = {
|
|||
[ServiceIdKind.PNI]: 'maxKyberPreKeyIdPNI',
|
||||
};
|
||||
|
||||
const LAST_RESORT_KEY_ARCHIVE_AGE = 30 * DAY;
|
||||
const LAST_RESORT_KEY_ROTATION_AGE = DAY * 1.5;
|
||||
const LAST_RESORT_KEY_MINIMUM = 5;
|
||||
const LAST_RESORT_KEY_UPDATE_TIME_KEY: StorageKeyByServiceIdKind = {
|
||||
|
@ -96,7 +96,6 @@ const PRE_KEY_ID_KEY: StorageKeyByServiceIdKind = {
|
|||
};
|
||||
const PRE_KEY_MINIMUM = 10;
|
||||
|
||||
const SIGNED_PRE_KEY_ARCHIVE_AGE = 30 * DAY;
|
||||
export const SIGNED_PRE_KEY_ID_KEY: StorageKeyByServiceIdKind = {
|
||||
[ServiceIdKind.ACI]: 'signedKeyId',
|
||||
[ServiceIdKind.Unknown]: 'signedKeyId',
|
||||
|
@ -756,7 +755,7 @@ export default class AccountManager extends EventTarget {
|
|||
'confirmed'
|
||||
);
|
||||
|
||||
// Keep SIGNED_PRE_KEY_MINIMUM keys, drop if older than SIGNED_PRE_KEY_ARCHIVE_AGE
|
||||
// Keep SIGNED_PRE_KEY_MINIMUM keys, drop if older than message queue time
|
||||
|
||||
const toDelete: Array<number> = [];
|
||||
sortedKeys.forEach((key, index) => {
|
||||
|
@ -765,7 +764,7 @@ export default class AccountManager extends EventTarget {
|
|||
}
|
||||
const createdAt = key.created_at || 0;
|
||||
|
||||
if (isOlderThan(createdAt, SIGNED_PRE_KEY_ARCHIVE_AGE)) {
|
||||
if (isOlderThan(createdAt, getMessageQueueTime())) {
|
||||
const timestamp = new Date(createdAt).toJSON();
|
||||
const confirmedText = key.confirmed ? ' (confirmed)' : '';
|
||||
log.info(
|
||||
|
@ -813,7 +812,7 @@ export default class AccountManager extends EventTarget {
|
|||
'confirmed'
|
||||
);
|
||||
|
||||
// Keep LAST_RESORT_KEY_MINIMUM keys, drop if older than LAST_RESORT_KEY_ARCHIVE_AGE
|
||||
// Keep LAST_RESORT_KEY_MINIMUM keys, drop if older than message queue time
|
||||
|
||||
const toDelete: Array<number> = [];
|
||||
sortedKeys.forEach((key, index) => {
|
||||
|
@ -822,7 +821,7 @@ export default class AccountManager extends EventTarget {
|
|||
}
|
||||
const createdAt = key.createdAt || 0;
|
||||
|
||||
if (isOlderThan(createdAt, LAST_RESORT_KEY_ARCHIVE_AGE)) {
|
||||
if (isOlderThan(createdAt, getMessageQueueTime())) {
|
||||
const timestamp = new Date(createdAt).toJSON();
|
||||
const confirmedText = key.isConfirmed ? ' (confirmed)' : '';
|
||||
log.info(
|
||||
|
|
|
@ -30,6 +30,7 @@ import { strictAssert } from '../util/assert';
|
|||
import type { SignalService as Proto } from '../protobuf';
|
||||
import { isMoreRecentThan } from '../util/timestamp';
|
||||
import { DAY } from '../util/durations';
|
||||
import { getMessageQueueTime } from '../util/getMessageQueueTime';
|
||||
import { getLocalAttachmentUrl } from '../util/getLocalAttachmentUrl';
|
||||
import type { ReencryptionInfo } from '../AttachmentCrypto';
|
||||
|
||||
|
@ -1156,10 +1157,9 @@ export function isReencryptableWithNewEncryptionInfo(
|
|||
);
|
||||
}
|
||||
|
||||
const TIME_ON_TRANSIT_TIER = 30 * DAY;
|
||||
// Extend range in case the attachment is actually still there (this function is meant to
|
||||
// be optimistic)
|
||||
const BUFFERED_TIME_ON_TRANSIT_TIER = TIME_ON_TRANSIT_TIER + 5 * DAY;
|
||||
const BUFFER_TIME_ON_TRANSIT_TIER = 5 * DAY;
|
||||
|
||||
export function mightStillBeOnTransitTier(
|
||||
attachment: Pick<AttachmentType, 'cdnKey' | 'cdnNumber' | 'uploadTimestamp'>
|
||||
|
@ -1177,7 +1177,10 @@ export function mightStillBeOnTransitTier(
|
|||
}
|
||||
|
||||
if (
|
||||
isMoreRecentThan(attachment.uploadTimestamp, BUFFERED_TIME_ON_TRANSIT_TIER)
|
||||
isMoreRecentThan(
|
||||
attachment.uploadTimestamp,
|
||||
getMessageQueueTime() + BUFFER_TIME_ON_TRANSIT_TIER
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -15,7 +15,6 @@ import {
|
|||
type CallHistoryDetails,
|
||||
CallMode,
|
||||
} from '../types/CallDisposition';
|
||||
import { DAY } from './durations';
|
||||
|
||||
export const CALL_LINK_DEFAULT_STATE: Pick<
|
||||
CallLinkType,
|
||||
|
@ -28,8 +27,6 @@ export const CALL_LINK_DEFAULT_STATE: Pick<
|
|||
storageNeedsSync: false,
|
||||
};
|
||||
|
||||
export const CALL_LINK_DELETED_STORAGE_RECORD_TTL = 30 * DAY;
|
||||
|
||||
export function getKeyFromCallLink(callLink: string): string {
|
||||
const url = new URL(callLink);
|
||||
if (url == null) {
|
||||
|
|
18
ts/util/getMessageQueueTime.ts
Normal file
18
ts/util/getMessageQueueTime.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
// Copyright 2024 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import * as RemoteConfig from '../RemoteConfig';
|
||||
import { MONTH, SECOND } from './durations';
|
||||
import { parseIntWithFallback } from './parseIntWithFallback';
|
||||
|
||||
export function getMessageQueueTime(): number {
|
||||
return (
|
||||
Math.max(
|
||||
parseIntWithFallback(
|
||||
RemoteConfig.getValue('global.messageQueueTimeInSeconds'),
|
||||
MONTH / SECOND
|
||||
),
|
||||
MONTH / SECOND
|
||||
) * SECOND
|
||||
);
|
||||
}
|
|
@ -3,7 +3,8 @@
|
|||
|
||||
import type { ServiceIdString } from '../types/ServiceId';
|
||||
import { isMoreRecentThan, isOlderThan } from './timestamp';
|
||||
import { HOUR, MONTH } from './durations';
|
||||
import { HOUR } from './durations';
|
||||
import { getMessageQueueTime } from './getMessageQueueTime';
|
||||
|
||||
const SIX_HOURS = 6 * HOUR;
|
||||
|
||||
|
@ -45,6 +46,7 @@ export function isConversationUnregisteredAndStale({
|
|||
}
|
||||
|
||||
return Boolean(
|
||||
firstUnregisteredAt && isOlderThan(firstUnregisteredAt, MONTH)
|
||||
firstUnregisteredAt &&
|
||||
isOlderThan(firstUnregisteredAt, getMessageQueueTime())
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue