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