Disappearing message cleanups

This commit is contained in:
Evan Hahn 2021-06-16 17:20:17 -05:00 committed by GitHub
parent dfa6fb5d61
commit 03a187097f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 149 additions and 268 deletions

View file

@ -1,4 +1,4 @@
// Copyright 2016-2020 Signal Messenger, LLC
// Copyright 2016-2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
/* global
@ -96,11 +96,6 @@
sent: true,
});
if (message.isExpiring() && !expirationStartTimestamp) {
// TODO DESKTOP-1509: use setToExpire once this is TS
await message.setToExpire(false, { skipSave: true });
}
window.Signal.Util.queueUpdateMessage(message.attributes);
// notify frontend listeners

View file

@ -1,4 +1,4 @@
// Copyright 2016-2020 Signal Messenger, LLC
// Copyright 2016-2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
/* global
@ -17,6 +17,9 @@
const messages = await window.Signal.Data.getExpiredMessages({
MessageCollection: Whisper.MessageCollection,
});
window.log.info(
`destroyExpiredMessages: found ${messages.length} messages to expire`
);
const messageIds = [];
const inMemoryMessages = [];
@ -59,20 +62,15 @@
let timeout;
async function checkExpiringMessages() {
// Look up the next expiring message and set a timer to destroy it
const message = await window.Signal.Data.getNextExpiringMessage({
Message: Whisper.Message,
});
window.log.info('checkExpiringMessages: checking for expiring messages');
if (!message) {
const soonestExpiry = await window.Signal.Data.getSoonestMessageExpiry();
if (!soonestExpiry) {
window.log.info('checkExpiringMessages: found no messages to expire');
return;
}
const expiresAt = message.get('expires_at');
Whisper.ExpiringMessagesListener.nextExpiration = expiresAt;
window.log.info('next message expires', new Date(expiresAt).toISOString());
let wait = expiresAt - Date.now();
let wait = soonestExpiry - Date.now();
// In the past
if (wait < 0) {
@ -84,6 +82,12 @@
wait = 2147483647;
}
window.log.info(
`checkExpiringMessages: next message expires ${new Date(
soonestExpiry
).toISOString()}; waiting ${wait} ms before clearing`
);
clearTimeout(timeout);
timeout = setTimeout(destroyExpiredMessages, wait);
}
@ -93,7 +97,6 @@
);
Whisper.ExpiringMessagesListener = {
nextExpiration: null,
init(events) {
checkExpiringMessages();
events.on('timetravel', debouncedCheckExpiringMessages);

View file

@ -1,4 +1,4 @@
// Copyright 2016-2020 Signal Messenger, LLC
// Copyright 2016-2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
/* global
@ -97,11 +97,6 @@
sent: true,
});
if (message.isExpiring() && !expirationStartTimestamp) {
// TODO DESKTOP-1509: use setToExpire once this is TS
await message.setToExpire(false, { skipSave: true });
}
window.Signal.Util.queueUpdateMessage(message.attributes);
// notify frontend listeners

View file

@ -1,4 +1,4 @@
// Copyright 2017-2020 Signal Messenger, LLC
// Copyright 2017-2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
/* global
@ -120,10 +120,6 @@
);
message.set({ expirationStartTimestamp });
const force = true;
// TODO DESKTOP-1509: use setToExpire once this is TS
await message.setToExpire(force, { skipSave: true });
const conversation = message.getConversation();
if (conversation) {
conversation.trigger('expiration-change', message);

View file

@ -24,14 +24,13 @@ import { routineProfileRefresh } from './routineProfileRefresh';
import { isMoreRecentThan, isOlderThan } from './util/timestamp';
import { isValidReactionEmoji } from './reactions/isValidReactionEmoji';
import { ConversationModel } from './models/conversations';
import { getMessageById } from './models/messages';
import { getMessageById, MessageModel } from './models/messages';
import { createBatcher } from './util/batcher';
import { updateConversationsWithUuidLookup } from './updateConversationsWithUuidLookup';
import { initializeAllJobQueues } from './jobs/initializeAllJobQueues';
import { removeStorageKeyJobQueue } from './jobs/removeStorageKeyJobQueue';
import { ourProfileKeyService } from './services/ourProfileKey';
import { shouldRespondWithProfileKey } from './util/shouldRespondWithProfileKey';
import { setToExpire } from './services/MessageUpdater';
import { LatestQueue } from './util/LatestQueue';
import { parseIntOrThrow } from './util/parseIntOrThrow';
import {
@ -1642,7 +1641,7 @@ export async function startApp(): Promise<void> {
window.dispatchEvent(new Event('storage_ready'));
window.log.info('Cleanup: starting...');
const messagesForCleanup = await window.Signal.Data.getOutgoingWithoutExpiresAt(
const messagesForCleanup = await window.Signal.Data.getOutgoingWithoutExpirationStartTimestamp(
{
MessageCollection: window.Whisper.MessageCollection,
}
@ -1652,11 +1651,13 @@ export async function startApp(): Promise<void> {
);
await Promise.all(
messagesForCleanup.map(async message => {
assert(
!message.get('expirationStartTimestamp'),
'Cleanup should not have messages with an expirationStartTimestamp'
);
const delivered = message.get('delivered');
const sentAt = message.get('sent_at');
const expirationStartTimestamp = message.get(
'expirationStartTimestamp'
);
if (message.hasErrors()) {
return;
@ -1666,12 +1667,7 @@ export async function startApp(): Promise<void> {
window.log.info(
`Cleanup: Starting timer for delivered message ${sentAt}`
);
message.set(
setToExpire({
...message.attributes,
expirationStartTimestamp: expirationStartTimestamp || sentAt,
})
);
message.set('expirationStartTimestamp', sentAt);
return;
}
@ -1685,6 +1681,9 @@ export async function startApp(): Promise<void> {
}
})
);
if (messagesForCleanup.length) {
window.Whisper.ExpiringMessagesListener.update();
}
window.log.info('Cleanup: complete');
window.log.info('listening for registration events');
@ -2389,7 +2388,9 @@ export async function startApp(): Promise<void> {
messagesToSave.push(message.attributes);
}
});
await window.Signal.Data.saveMessages(messagesToSave, {});
await window.Signal.Data.saveMessages(messagesToSave, {
Message: MessageModel,
});
}
function onReconnect() {
// We disable notifications on first connect, but the same applies to reconnect. In

View file

@ -152,7 +152,6 @@ export type PropsData = {
isViewOnce: boolean;
};
previews: Array<LinkPreviewType>;
isExpired?: boolean;
isTapToView?: boolean;
isTapToViewExpired?: boolean;
@ -497,7 +496,7 @@ export class Message extends React.Component<Props, State> {
public checkExpired(): void {
const now = Date.now();
const { isExpired, expirationTimestamp, expirationLength } = this.props;
const { expirationTimestamp, expirationLength } = this.props;
if (!expirationTimestamp || !expirationLength) {
return;
@ -506,7 +505,7 @@ export class Message extends React.Component<Props, State> {
return;
}
if (isExpired || now >= expirationTimestamp) {
if (now >= expirationTimestamp) {
this.setState({
expiring: true,
});

View file

@ -1717,6 +1717,7 @@ export async function createGroupV2({
};
await window.Signal.Data.saveMessages([createdTheGroupMessage], {
forceSave: true,
Message: window.Whisper.Message,
});
const model = new window.Whisper.Message(createdTheGroupMessage);
window.MessageController.register(model.id, model);
@ -2831,6 +2832,7 @@ async function updateGroup(
if (changeMessagesToSave.length > 0) {
await window.Signal.Data.saveMessages(changeMessagesToSave, {
forceSave: true,
Message: window.Whisper.Message,
});
changeMessagesToSave.forEach(changeMessage => {
const model = new window.Whisper.Message(changeMessage);

1
ts/model-types.d.ts vendored
View file

@ -90,7 +90,6 @@ export type MessageAttributesType = {
errors?: Array<CustomError>;
expirationStartTimestamp: number | null;
expireTimer: number;
expires_at: number;
groupMigration?: GroupMigrationType;
group_update: {
avatarUpdated: boolean;

View file

@ -1234,9 +1234,6 @@ export class ConversationModel extends window.Backbone
}
addSingleMessage(message: MessageModel): MessageModel {
// TODO use MessageUpdater.setToExpire
message.setToExpire();
const { messagesAdded } = window.reduxActions.conversations;
const isNewMessage = true;
messagesAdded(

View file

@ -56,7 +56,7 @@ import { AttachmentType, isImage, isVideo } from '../types/Attachment';
import { MIMEType } from '../types/MIME';
import { LinkPreviewType } from '../types/message/LinkPreviews';
import { ourProfileKeyService } from '../services/ourProfileKey';
import { markRead, setToExpire } from '../services/MessageUpdater';
import { markRead } from '../services/MessageUpdater';
import {
isDirectConversation,
isGroupV1,
@ -256,8 +256,6 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
isSelected?: boolean;
hasExpired?: boolean;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
quotedMessage: any;
@ -281,10 +279,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
this.OUR_UUID = window.textsecure.storage.user.getUuid();
this.on('destroy', this.onDestroy);
this.on('change:expirationStartTimestamp', this.setToExpire);
this.on('change:expireTimer', this.setToExpire);
this.on('unload', this.unload);
this.setToExpire();
this.on('change', this.notifyRedux);
}
@ -1035,7 +1030,6 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
attachments: this.getAttachmentsForMessage(),
previews: this.getPropsForPreview(),
quote: this.getPropsForQuote(),
isExpired: this.hasExpired,
expirationLength,
expirationTimestamp,
reactions,
@ -2028,10 +2022,6 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
}
}
onExpired(): void {
this.hasExpired = true;
}
isUnidentifiedDelivery(
contactId: string,
lookup: Record<string, unknown>
@ -2163,29 +2153,6 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
return this.get('expireTimer') && this.get('expirationStartTimestamp');
}
setToExpire(force = false, options = {}): void {
this.set(setToExpire(this.attributes, { ...options, force }));
}
isExpired(): boolean {
return this.msTilExpire() <= 0;
}
msTilExpire(): number {
if (!this.isExpiring()) {
return Infinity;
}
const now = Date.now();
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const start = this.get('expirationStartTimestamp')!;
const delta = this.get('expireTimer') * 1000;
let msFromNow = start + delta - now;
if (msFromNow < 0) {
msFromNow = 0;
}
return msFromNow;
}
getIncomingContact(): ConversationModel | undefined | null {
if (!this.isIncoming()) {
return null;

View file

@ -42,46 +42,3 @@ export function getExpiresAt(
? messageAttrs.expirationStartTimestamp + expireTimerMs
: undefined;
}
export function setToExpire(
messageAttrs: MessageAttributesType,
{ force = false, skipSave = false } = {}
): MessageAttributesType {
if (!isExpiring(messageAttrs) || (!force && messageAttrs.expires_at)) {
return messageAttrs;
}
const expiresAt = getExpiresAt(messageAttrs);
if (!expiresAt) {
return messageAttrs;
}
const nextMessageAttributes = {
...messageAttrs,
expires_at: expiresAt,
};
window.log.info('Set message expiration', {
start: messageAttrs.expirationStartTimestamp,
expiresAt,
sentAt: messageAttrs.sent_at,
});
if (messageAttrs.id && !skipSave) {
window.Signal.Util.queueUpdateMessage(nextMessageAttributes);
}
return nextMessageAttributes;
}
function isExpiring(
messageAttrs: Pick<
MessageAttributesType,
'expireTimer' | 'expirationStartTimestamp'
>
): boolean {
return Boolean(
messageAttrs.expireTimer && messageAttrs.expirationStartTimestamp
);
}

View file

@ -189,8 +189,8 @@ const dataInterface: ClientInterface = {
getAllMessageIds,
getMessagesBySentAt,
getExpiredMessages,
getOutgoingWithoutExpiresAt,
getNextExpiringMessage,
getOutgoingWithoutExpirationStartTimestamp,
getSoonestMessageExpiry,
getNextTapToViewMessageTimestampToAgeOut,
getTapToViewMessagesNeedingErase,
getOlderMessagesByConversation,
@ -997,12 +997,13 @@ async function saveMessage(
async function saveMessages(
arrayOfMessages: Array<MessageType>,
{ forceSave }: { forceSave?: boolean } = {}
{ forceSave, Message }: { forceSave?: boolean; Message: typeof MessageModel }
) {
await channels.saveMessages(
arrayOfMessages.map(message => _cleanMessageData(message)),
{ forceSave }
);
Message.updateTimers();
}
async function removeMessage(
@ -1300,28 +1301,18 @@ async function getExpiredMessages({
return new MessageCollection(messages);
}
async function getOutgoingWithoutExpiresAt({
async function getOutgoingWithoutExpirationStartTimestamp({
MessageCollection,
}: {
MessageCollection: typeof MessageModelCollectionType;
}) {
const messages = await channels.getOutgoingWithoutExpiresAt();
const messages = await channels.getOutgoingWithoutExpirationStartTimestamp();
return new MessageCollection(messages);
}
async function getNextExpiringMessage({
Message,
}: {
Message: typeof MessageModel;
}) {
const message = await channels.getNextExpiringMessage();
if (message) {
return new Message(message);
}
return null;
function getSoonestMessageExpiry() {
return channels.getSoonestMessageExpiry();
}
async function getNextTapToViewMessageTimestampToAgeOut() {

View file

@ -221,10 +221,6 @@ export type DataInterface = {
getMessageCount: (conversationId?: string) => Promise<number>;
hasUserInitiatedMessages: (conversationId: string) => Promise<boolean>;
saveMessages: (
arrayOfMessages: Array<MessageType>,
options: { forceSave?: boolean }
) => Promise<void>;
getAllMessageIds: () => Promise<Array<string>>;
getMessageMetricsForConversation: (
conversationId: string
@ -313,6 +309,7 @@ export type DataInterface = {
getMessageServerGuidsForSpam: (
conversationId: string
) => Promise<Array<string>>;
getSoonestMessageExpiry: () => Promise<undefined | number>;
getJobsInQueue(queueType: string): Promise<Array<StoredJob>>;
insertJob(job: Readonly<StoredJob>): Promise<void>;
@ -370,8 +367,7 @@ export type ServerInterface = DataInterface & {
conversationId: string;
ourConversationId: string;
}) => Promise<MessageType | undefined>;
getNextExpiringMessage: () => Promise<MessageType | undefined>;
getOutgoingWithoutExpiresAt: () => Promise<Array<MessageType>>;
getOutgoingWithoutExpirationStartTimestamp: () => Promise<Array<MessageType>>;
getTapToViewMessagesNeedingErase: () => Promise<Array<MessageType>>;
getUnreadCountForConversation: (conversationId: string) => Promise<number>;
getUnreadByConversationAndMarkRead: (
@ -416,6 +412,10 @@ export type ServerInterface = DataInterface & {
data: MessageType,
options: { forceSave?: boolean }
) => Promise<string>;
saveMessages: (
arrayOfMessages: Array<MessageType>,
options: { forceSave?: boolean }
) => Promise<void>;
updateConversation: (data: ConversationType) => Promise<void>;
// For testing only
@ -505,10 +505,7 @@ export type ClientInterface = DataInterface & {
ourConversationId: string;
Message: typeof MessageModel;
}) => Promise<MessageModel | undefined>;
getNextExpiringMessage: (options: {
Message: typeof MessageModel;
}) => Promise<MessageModel | null>;
getOutgoingWithoutExpiresAt: (options: {
getOutgoingWithoutExpirationStartTimestamp: (options: {
MessageCollection: typeof MessageModelCollectionType;
}) => Promise<MessageModelCollectionType>;
getTapToViewMessagesNeedingErase: (options: {
@ -557,6 +554,10 @@ export type ClientInterface = DataInterface & {
data: MessageType,
options: { forceSave?: boolean; Message: typeof MessageModel }
) => Promise<string>;
saveMessages: (
arrayOfMessages: Array<MessageType>,
options: { forceSave?: boolean; Message: typeof MessageModel }
) => Promise<void>;
searchMessages: (
query: string,
options?: { limit?: number }

View file

@ -33,10 +33,8 @@ import { ReactionType } from '../types/Reactions';
import { StoredJob } from '../jobs/types';
import { assert } from '../util/assert';
import { combineNames } from '../util/combineNames';
import { getExpiresAt } from '../services/MessageUpdater';
import { isNormalNumber } from '../util/isNormalNumber';
import { isNotNil } from '../util/isNotNil';
import * as iterables from '../util/iterables';
import { ConversationColorType, CustomColorType } from '../types/Colors';
import {
@ -180,8 +178,8 @@ const dataInterface: ServerInterface = {
getAllMessageIds,
getMessagesBySentAt,
getExpiredMessages,
getOutgoingWithoutExpiresAt,
getNextExpiringMessage,
getOutgoingWithoutExpirationStartTimestamp,
getSoonestMessageExpiry,
getNextTapToViewMessageTimestampToAgeOut,
getTapToViewMessagesNeedingErase,
getOlderMessagesByConversation,
@ -1859,6 +1857,38 @@ function updateToSchemaVersion32(currentVersion: number, db: Database) {
console.log('updateToSchemaVersion32: success!');
}
function updateToSchemaVersion33(currentVersion: number, db: Database) {
if (currentVersion >= 33) {
return;
}
db.transaction(() => {
db.exec(`
-- These indexes should exist, but we add "IF EXISTS" for safety.
DROP INDEX IF EXISTS messages_expires_at;
DROP INDEX IF EXISTS messages_without_timer;
ALTER TABLE messages
ADD COLUMN
expiresAt INT
GENERATED ALWAYS
AS (expirationStartTimestamp + (expireTimer * 1000));
CREATE INDEX message_expires_at ON messages (
expiresAt
);
CREATE INDEX outgoing_messages_without_expiration_start_timestamp ON messages (
expireTimer, expirationStartTimestamp, type
)
WHERE expireTimer IS NOT NULL AND expirationStartTimestamp IS NULL;
`);
db.pragma('user_version = 33');
})();
console.log('updateToSchemaVersion33: success!');
}
const SCHEMA_VERSIONS = [
updateToSchemaVersion1,
updateToSchemaVersion2,
@ -1892,6 +1922,7 @@ const SCHEMA_VERSIONS = [
updateToSchemaVersion30,
updateToSchemaVersion31,
updateToSchemaVersion32,
updateToSchemaVersion33,
];
function updateSchema(db: Database): void {
@ -2997,7 +3028,6 @@ function saveMessageSync(
const {
body,
conversationId,
expires_at,
hasAttachments,
hasFileAttachments,
hasVisualMediaAttachments,
@ -3024,7 +3054,6 @@ function saveMessageSync(
body: body || null,
conversationId,
expirationStartTimestamp: expirationStartTimestamp || null,
expires_at: expires_at || null,
expireTimer: expireTimer || null,
hasAttachments: hasAttachments ? 1 : 0,
hasFileAttachments: hasFileAttachments ? 1 : 0,
@ -3053,7 +3082,6 @@ function saveMessageSync(
body = $body,
conversationId = $conversationId,
expirationStartTimestamp = $expirationStartTimestamp,
expires_at = $expires_at,
expireTimer = $expireTimer,
hasAttachments = $hasAttachments,
hasFileAttachments = $hasFileAttachments,
@ -3091,7 +3119,6 @@ function saveMessageSync(
body,
conversationId,
expirationStartTimestamp,
expires_at,
expireTimer,
hasAttachments,
hasFileAttachments,
@ -3114,7 +3141,6 @@ function saveMessageSync(
$body,
$conversationId,
$expirationStartTimestamp,
$expires_at,
$expireTimer,
$hasAttachments,
$hasFileAttachments,
@ -3243,31 +3269,6 @@ async function getMessageBySender({
return rows.map(row => jsonToObject(row.json));
}
function getExpireData(
expireTimer: number,
readAt?: number
): {
expirationStartTimestamp: number;
expiresAt: number;
} {
const expirationStartTimestamp = Math.min(Date.now(), readAt || Date.now());
const expiresAt = getExpiresAt({
expireTimer,
expirationStartTimestamp,
});
// We are guaranteeing an expirationStartTimestamp above so this should
// definitely return a number.
if (!expiresAt || typeof expiresAt !== 'number') {
assert(false, 'Expected expiresAt to be a number');
}
return {
expirationStartTimestamp,
expiresAt,
};
}
async function getUnreadCountForConversation(
conversationId: string
): Promise<number> {
@ -3296,10 +3297,33 @@ async function getUnreadByConversationAndMarkRead(
> {
const db = getInstance();
return db.transaction(() => {
const expirationStartTimestamp = Math.min(Date.now(), readAt ?? Infinity);
db.prepare<Query>(
`
UPDATE messages
SET
expirationStartTimestamp = $expirationStartTimestamp,
json = json_patch(json, $jsonPatch)
WHERE
(
expirationStartTimestamp IS NULL OR
expirationStartTimestamp > $expirationStartTimestamp
) AND
expireTimer IS NOT NULL AND
conversationId = $conversationId AND
received_at <= $newestUnreadId;
`
).run({
conversationId,
expirationStartTimestamp,
jsonPatch: JSON.stringify({ expirationStartTimestamp }),
newestUnreadId,
});
const rows = db
.prepare<Query>(
`
SELECT id, expires_at, expireTimer, expirationStartTimestamp, json
SELECT id, json
FROM messages WHERE
unread = $unread AND
conversationId = $conversationId AND
@ -3313,10 +3337,6 @@ async function getUnreadByConversationAndMarkRead(
newestUnreadId,
});
if (!rows.length) {
return [];
}
db.prepare<Query>(
`
UPDATE messages
@ -3335,58 +3355,18 @@ async function getUnreadByConversationAndMarkRead(
unread: 1,
});
const rowsWithExpireTimers = iterables.filter(rows, row => row.expireTimer);
const rowsNeedingExpirationUpdates = iterables.filter(
rowsWithExpireTimers,
row =>
!row.expirationStartTimestamp ||
!row.expires_at ||
getExpireData(row.expireTimer, readAt).expirationStartTimestamp <
row.expirationStartTimestamp
);
const expirationStartTimestampUpdates: Iterable<{
id: string;
expirationStartTimestamp: number;
expiresAt: number;
}> = iterables.map(rowsNeedingExpirationUpdates, row => ({
id: row.id,
...getExpireData(row.expireTimer, readAt),
}));
const stmt = db.prepare<Query>(
`
UPDATE messages
SET
expirationStartTimestamp = $expirationStartTimestamp,
expires_at = $expiresAt
WHERE
id = $id;
`
);
const updatedExpireDataByRowId = new Map<
string,
{
expirationStartTimestamp: number;
expiresAt: number;
}
>();
for (const update of expirationStartTimestampUpdates) {
stmt.run(update);
updatedExpireDataByRowId.set(update.id, update);
}
return rows.map(row => {
const json = jsonToObject(row.json);
const updatedExpireData = updatedExpireDataByRowId.get(row.id);
return {
unread: false,
...pick(json, ['id', 'sent_at', 'source', 'sourceUuid', 'type']),
...(updatedExpireData
? {
expirationStartTimestamp:
updatedExpireData.expirationStartTimestamp,
expires_at: updatedExpireData.expiresAt,
}
: {}),
...pick(json, [
'expirationStartTimestamp',
'id',
'sent_at',
'source',
'sourceUuid',
'type',
]),
};
});
})();
@ -3916,30 +3896,29 @@ async function getExpiredMessages(): Promise<Array<MessageType>> {
.prepare<Query>(
`
SELECT json FROM messages WHERE
expires_at IS NOT NULL AND
expires_at <= $expires_at
ORDER BY expires_at ASC;
expiresAt IS NOT NULL AND
expiresAt <= $now
ORDER BY expiresAt ASC;
`
)
.all({
expires_at: now,
});
.all({ now });
return rows.map(row => jsonToObject(row.json));
}
async function getOutgoingWithoutExpiresAt(): Promise<Array<MessageType>> {
async function getOutgoingWithoutExpirationStartTimestamp(): Promise<
Array<MessageType>
> {
const db = getInstance();
const rows: JSONRows = db
.prepare<EmptyQuery>(
`
SELECT json FROM messages
INDEXED BY messages_without_timer
INDEXED BY outgoing_messages_without_expiration_start_timestamp
WHERE
expireTimer > 0 AND
expires_at IS NULL AND
type IS 'outgoing'
ORDER BY expires_at ASC;
expirationStartTimestamp IS NULL AND
type IS 'outgoing';
`
)
.all();
@ -3947,26 +3926,21 @@ async function getOutgoingWithoutExpiresAt(): Promise<Array<MessageType>> {
return rows.map(row => jsonToObject(row.json));
}
async function getNextExpiringMessage(): Promise<MessageType | undefined> {
async function getSoonestMessageExpiry(): Promise<undefined | number> {
const db = getInstance();
// Note: we avoid 'IS NOT NULL' here because it does seem to bypass our index
const rows: JSONRows = db
// Note: we use `pluck` to only get the first column.
const result: null | number = db
.prepare<EmptyQuery>(
`
SELECT json FROM messages
WHERE expires_at > 0
ORDER BY expires_at ASC
LIMIT 1;
SELECT MIN(expiresAt)
FROM messages;
`
)
.all();
.pluck(true)
.get();
if (!rows || rows.length < 1) {
return undefined;
}
return jsonToObject(rows[0].json);
return result || undefined;
}
async function getNextTapToViewMessageTimestampToAgeOut(): Promise<

View file

@ -1,4 +1,4 @@
// Copyright 2018-2020 Signal Messenger, LLC
// Copyright 2018-2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
/* eslint-disable camelcase */
@ -56,7 +56,6 @@ export type OutgoingMessage = Readonly<
// Optional
body?: string;
expires_at?: number;
expireTimer?: number;
messageTimer?: number; // deprecated
isViewOnce?: number;

View file

@ -11,7 +11,9 @@ const updateMessageBatcher = createBatcher<MessageAttributesType>({
maxSize: 50,
processBatch: async (messageAttrs: Array<MessageAttributesType>) => {
window.log.info('updateMessageBatcher', messageAttrs.length);
await window.Signal.Data.saveMessages(messageAttrs, {});
await window.Signal.Data.saveMessages(messageAttrs, {
Message: window.Whisper.Message,
});
},
});
@ -37,6 +39,9 @@ export const saveNewMessageBatcher = createWaitBatcher<MessageAttributesType>({
maxSize: 30,
processBatch: async (messageAttrs: Array<MessageAttributesType>) => {
window.log.info('saveNewMessageBatcher', messageAttrs.length);
await window.Signal.Data.saveMessages(messageAttrs, { forceSave: true });
await window.Signal.Data.saveMessages(messageAttrs, {
forceSave: true,
Message: window.Whisper.Message,
});
},
});