Receive support for editing messages

This commit is contained in:
Josh Perez 2023-03-27 19:48:57 -04:00 committed by GitHub
parent 2781e621ad
commit 36e21c0134
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
46 changed files with 2053 additions and 405 deletions

View file

@ -17,6 +17,7 @@ import type {
import type { MessageModel } from '../models/messages';
import type { AttachmentType } from '../types/Attachment';
import { getAttachmentSignature, isDownloaded } from '../types/Attachment';
import * as Errors from '../types/errors';
import type { LoggerType } from '../types/Logging';
import * as log from '../logging/log';
@ -433,7 +434,7 @@ function _markAttachmentAsPermanentError(
attachment: AttachmentType
): AttachmentType {
return {
...omit(attachment, ['key', 'digest', 'id']),
...omit(attachment, ['key', 'id']),
error: true,
};
}
@ -454,6 +455,7 @@ async function _addAttachmentToMessage(
}
const logPrefix = `${message.idForLogging()} (type: ${type}, index: ${index})`;
const attachmentSignature = getAttachmentSignature(attachment);
if (type === 'long-message') {
// Attachment wasn't downloaded yet.
@ -482,13 +484,60 @@ async function _addAttachmentToMessage(
if (type === 'attachment') {
const attachments = message.get('attachments');
let handledInEditHistory = false;
const editHistory = message.get('editHistory');
if (editHistory) {
const newEditHistory = editHistory.map(edit => {
if (!edit.attachments) {
return edit;
}
return {
...edit,
// Loop through all the attachments to find the attachment we intend
// to replace.
attachments: edit.attachments.map(editAttachment => {
if (isDownloaded(editAttachment)) {
return editAttachment;
}
if (
attachmentSignature !== getAttachmentSignature(editAttachment)
) {
return editAttachment;
}
handledInEditHistory = true;
return attachment;
}),
};
});
if (newEditHistory !== editHistory) {
message.set({ editHistory: newEditHistory });
}
}
if (!attachments || attachments.length <= index) {
throw new Error(
`_addAttachmentToMessage: attachments didn't exist or ${index} was too large`
`_addAttachmentToMessage: attachments didn't exist or index(${index}) was too large`
);
}
// Verify attachment is still valid
const isSameAttachment =
attachments[index] &&
getAttachmentSignature(attachments[index]) === attachmentSignature;
if (handledInEditHistory && !isSameAttachment) {
return;
}
strictAssert(isSameAttachment, `${logPrefix} mismatched attachment`);
_checkOldAttachment(attachments, index.toString(), logPrefix);
// Replace attachment
const newAttachments = [...attachments];
newAttachments[index] = attachment;
@ -499,6 +548,48 @@ async function _addAttachmentToMessage(
if (type === 'preview') {
const preview = message.get('preview');
let handledInEditHistory = false;
const editHistory = message.get('editHistory');
if (preview && editHistory) {
const newEditHistory = editHistory.map(edit => {
if (!edit.preview || edit.preview.length <= index) {
return edit;
}
const item = edit.preview[index];
if (!item) {
return edit;
}
if (
item.image &&
(isDownloaded(item.image) ||
attachmentSignature !== getAttachmentSignature(item.image))
) {
return edit;
}
const newPreview = [...edit.preview];
newPreview[index] = {
...edit.preview[index],
image: attachment,
};
handledInEditHistory = true;
return {
...edit,
preview: newPreview,
};
});
if (newEditHistory !== editHistory) {
message.set({ editHistory: newEditHistory });
}
}
if (!preview || preview.length <= index) {
throw new Error(
`_addAttachmentToMessage: preview didn't exist or ${index} was too large`
@ -509,8 +600,16 @@ async function _addAttachmentToMessage(
throw new Error(`_addAttachmentToMessage: preview ${index} was falsey`);
}
// Verify attachment is still valid
const isSameAttachment =
item.image && getAttachmentSignature(item.image) === attachmentSignature;
if (handledInEditHistory && !isSameAttachment) {
return;
}
strictAssert(isSameAttachment, `${logPrefix} mismatched attachment`);
_checkOldAttachment(item, 'image', logPrefix);
// Replace attachment
const newPreview = [...preview];
newPreview[index] = {
...preview[index],

View file

@ -0,0 +1,104 @@
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { MessageAttributesType } from '../model-types.d';
import type { MessageModel } from '../models/messages';
import type { ProcessedDataMessage } from '../textsecure/Types.d';
import * as Errors from '../types/errors';
import * as log from '../logging/log';
import { drop } from '../util/drop';
import { filter, size } from '../util/iterables';
import { getContactId } from '../messages/helpers';
import { handleEditMessage } from '../util/handleEditMessage';
export type EditAttributesType = {
dataMessage: ProcessedDataMessage;
fromId: string;
message: MessageAttributesType;
targetSentTimestamp: number;
};
const edits = new Set<EditAttributesType>();
export function forMessage(message: MessageModel): Array<EditAttributesType> {
const matchingEdits = filter(edits, item => {
return (
item.targetSentTimestamp === message.get('sent_at') &&
item.fromId === getContactId(message.attributes)
);
});
if (size(matchingEdits) > 0) {
log.info('Edits.forMessage: Found early edit for message');
filter(matchingEdits, item => edits.delete(item));
return Array.from(matchingEdits);
}
return [];
}
export async function onEdit(edit: EditAttributesType): Promise<void> {
edits.add(edit);
try {
// The conversation the deleted message was in; we have to find it in the database
// to to figure that out.
const targetConversation =
await window.ConversationController.getConversationForTargetMessage(
edit.fromId,
edit.targetSentTimestamp
);
if (!targetConversation) {
log.info(
'No target conversation for edit',
edit.fromId,
edit.targetSentTimestamp
);
return;
}
// Do not await, since this can deadlock the queue
drop(
targetConversation.queueJob('Edits.onEdit', async () => {
log.info('Handling edit for', {
targetSentTimestamp: edit.targetSentTimestamp,
sentAt: edit.dataMessage.timestamp,
});
const messages = await window.Signal.Data.getMessagesBySentAt(
edit.targetSentTimestamp
);
// Verify authorship
const targetMessage = messages.find(
m =>
edit.message.conversationId === m.conversationId &&
edit.fromId === getContactId(m)
);
if (!targetMessage) {
log.info(
'No message for edit',
edit.fromId,
edit.targetSentTimestamp
);
return;
}
const message = window.MessageController.register(
targetMessage.id,
targetMessage
);
await handleEditMessage(message.attributes, edit);
edits.delete(edit);
})
);
} catch (error) {
log.error('Edits.onEdit error:', Errors.toLogFormat(error));
}
}

View file

@ -285,9 +285,10 @@ export class MessageReceipts extends Collection<MessageReceiptModel> {
const type = receipt.get('type');
try {
const messages = await window.Signal.Data.getMessagesBySentAt(
messageSentAt
);
const messages =
await window.Signal.Data.getMessagesIncludingEditedBySentAt(
messageSentAt
);
const message = await getTargetMessage(
sourceConversationId,

View file

@ -82,9 +82,10 @@ export class ReadSyncs extends Collection {
async onSync(sync: ReadSyncModel): Promise<void> {
try {
const messages = await window.Signal.Data.getMessagesBySentAt(
sync.get('timestamp')
);
const messages =
await window.Signal.Data.getMessagesIncludingEditedBySentAt(
sync.get('timestamp')
);
const found = messages.find(item => {
const sender = window.ConversationController.lookupOrCreate({

View file

@ -98,7 +98,12 @@ export class ViewSyncs extends Collection {
const attachments = message.get('attachments');
if (!attachments?.every(isDownloaded)) {
void queueAttachmentDownloads(message.attributes);
const updatedFields = await queueAttachmentDownloads(
message.attributes
);
if (updatedFields) {
message.set(updatedFields);
}
}
}