signal-desktop/ts/messageModifiers/DeletesForMe.ts

133 lines
3.8 KiB
TypeScript

// Copyright 2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import * as log from '../logging/log';
import * as Errors from '../types/errors';
import { drop } from '../util/drop';
import { getMessageSentTimestampSet } from '../util/getMessageSentTimestampSet';
import type { MessageAttributesType } from '../model-types';
import type {
ConversationToDelete,
MessageToDelete,
} from '../textsecure/messageReceiverEvents';
import {
deleteAttachmentFromMessage,
deleteMessage,
doesMessageMatch,
getConversationFromTarget,
getMessageQueryFromTarget,
} from '../util/deleteForMe';
import { DataWriter } from '../sql/Client';
const { removeSyncTaskById } = DataWriter;
export type DeleteForMeAttributesType = {
conversation: ConversationToDelete;
deleteAttachmentData?: {
clientUuid?: string;
fallbackDigest?: string;
fallbackPlaintextHash?: string;
};
envelopeId: string;
message: MessageToDelete;
syncTaskId: string;
timestamp: number;
};
const deletes = new Map<string, DeleteForMeAttributesType>();
async function remove(item: DeleteForMeAttributesType): Promise<void> {
const { syncTaskId } = item;
await removeSyncTaskById(syncTaskId);
deletes.delete(syncTaskId);
}
export async function forMessage(
messageAttributes: MessageAttributesType
): Promise<Array<DeleteForMeAttributesType>> {
const sentTimestamps = getMessageSentTimestampSet(messageAttributes);
const deleteValues = Array.from(deletes.values());
const matchingDeletes = deleteValues.filter(item => {
const itemConversation = getConversationFromTarget(item.conversation);
const query = getMessageQueryFromTarget(item.message);
if (!itemConversation) {
return false;
}
return doesMessageMatch({
conversationId: itemConversation.id,
message: messageAttributes,
query,
sentTimestamps,
});
});
if (!matchingDeletes.length) {
return [];
}
log.info('Found early DeleteForMe for message');
await Promise.all(
matchingDeletes.map(async item => {
await remove(item);
})
);
return matchingDeletes;
}
export async function onDelete(item: DeleteForMeAttributesType): Promise<void> {
try {
const conversation = getConversationFromTarget(item.conversation);
const message = getMessageQueryFromTarget(item.message);
const logId = `DeletesForMe.onDelete(sentAt=${message.sentAt},timestamp=${item.timestamp},envelopeId=${item.envelopeId})`;
deletes.set(item.syncTaskId, item);
if (!conversation) {
log.warn(`${logId}: Conversation not found!`);
await remove(item);
return;
}
// Do not await, since this a can deadlock the queue
drop(
conversation.queueJob('DeletesForMe.onDelete', async () => {
log.info(`${logId}: Starting...`);
let result: boolean;
if (item.deleteAttachmentData) {
// This will find the message, then work with a backbone model to mirror what
// modifyTargetMessage does.
result = await deleteAttachmentFromMessage(
conversation.id,
item.message,
item.deleteAttachmentData,
{
deleteOnDisk: window.Signal.Migrations.deleteAttachmentData,
deleteDownloadOnDisk: window.Signal.Migrations.deleteDownloadData,
logId,
}
);
} else {
// This automatically notifies redux
result = await deleteMessage(conversation.id, item.message, logId);
}
if (result) {
await remove(item);
}
log.info(`${logId}: Complete (result=${result})`);
})
);
} catch (error) {
log.error(
`DeletesForMe.onDelete(task=${item.syncTaskId},envelopeId=${item.envelopeId}): Error`,
Errors.toLogFormat(error)
);
await remove(item);
}
}