From 5d9565a559e44a1d51562f1df58b84cb89d0e1ae Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Tue, 26 Oct 2021 16:07:13 +1100 Subject: [PATCH] fix menu action for unsending messages --- preload.js | 2 +- .../conversations/unsendingInteractions.ts | 103 +++++++++++------- ts/receiver/contentMessage.ts | 44 ++++---- ts/session/sending/MessageQueue.ts | 7 +- ts/session/utils/syncUtils.ts | 7 +- 5 files changed, 97 insertions(+), 66 deletions(-) diff --git a/preload.js b/preload.js index bfaa25609..d5bd8746d 100644 --- a/preload.js +++ b/preload.js @@ -43,7 +43,7 @@ window.lokiFeatureFlags = { useFileOnionRequestsV2: true, // more compact encoding of files in response padOutgoingAttachments: true, enablePinConversations: true, - useCallMessage: true, + useCallMessage: false, }; window.isBeforeVersion = (toCheck, baseVersion) => { diff --git a/ts/interactions/conversations/unsendingInteractions.ts b/ts/interactions/conversations/unsendingInteractions.ts index 9f75148a5..c2de30996 100644 --- a/ts/interactions/conversations/unsendingInteractions.ts +++ b/ts/interactions/conversations/unsendingInteractions.ts @@ -32,7 +32,6 @@ async function unsendMessagesForEveryone( } const unsendMsgObjects = getUnsendMessagesObjects(msgsToDelete); - let allDeleted = false; if (conversation.isPrivate()) { // sending to recipient all the messages separately for now await Promise.all( @@ -49,7 +48,6 @@ async function unsendMessagesForEveryone( .catch(window?.log?.error) ) ); - allDeleted = await deleteMessagesFromSwarmAndCompletelyLocally(conversation, msgsToDelete); } else if (conversation.isClosedGroup()) { // sending to recipient all the messages separately for now await Promise.all( @@ -59,18 +57,11 @@ async function unsendMessagesForEveryone( .catch(window?.log?.error) ) ); - - allDeleted = await deleteMessagesFromSwarmAndCompletelyLocally(conversation, msgsToDelete); - - return; } + await deleteMessagesFromSwarmAndCompletelyLocally(conversation, msgsToDelete); window.inboxStore?.dispatch(resetSelectedMessageIds()); - if (allDeleted) { - ToastUtils.pushDeleted(); - } else { - ToastUtils.someDeletionsFailed(); - } + ToastUtils.pushDeleted(); } function getUnsendMessagesObjects(messages: Array) { @@ -119,7 +110,7 @@ export async function deleteMessagesFromSwarmOnly(messages: Array) * Delete the messages from the swarm with an unsend request and if it worked, delete those messages locally. * If an error happened, we just return false, Toast an error, and do not remove the messages locally at all. */ -async function deleteMessagesFromSwarmAndCompletelyLocally( +export async function deleteMessagesFromSwarmAndCompletelyLocally( conversation: ConversationModel, messages: Array ) { @@ -128,14 +119,33 @@ async function deleteMessagesFromSwarmAndCompletelyLocally( window.log.warn( 'deleteMessagesFromSwarmAndCompletelyLocally: some messages failed to be deleted ' ); - return false; } await Promise.all( messages.map(async message => { return deleteMessageLocallyOnly({ conversation, message, deletionType: 'complete' }); }) ); - return true; +} + +/** + * Delete the messages from the swarm with an unsend request and if it worked, mark those messages locally as deleted but do not remove them. + * If an error happened, we still mark the message locally as deleted. + */ +export async function deleteMessagesFromSwarmAndMarkAsDeletedLocally( + conversation: ConversationModel, + messages: Array +) { + const deletedFromSwarm = await deleteMessagesFromSwarmOnly(messages); + if (!deletedFromSwarm) { + window.log.warn( + 'deleteMessagesFromSwarmAndMarkAsDeletedLocally: some messages failed to be deleted but still removing the messages content... ' + ); + } + await Promise.all( + messages.map(async message => { + return deleteMessageLocallyOnly({ conversation, message, deletionType: 'markDeleted' }); + }) + ); } /** @@ -164,25 +174,32 @@ export async function deleteMessageLocallyOnly({ } /** + * Send an UnsendMessage synced message so our devices removes those messages locally, + * and send an unsend request on our swarm so this message is effectively removed. * + * Show a toast on error/success and reset the selection */ -async function deleteJustForThisUser( +async function unsendMessageJustForThisUser( conversation: ConversationModel, msgsToDelete: Array ) { window?.log?.warn('Deleting messages just for this user'); - // is deleting on swarm sufficient or does it need to be unsent as well? - const deleteResult = await deleteMessagesFromSwarmAndCompletelyLocally( - conversation, - msgsToDelete + + const unsendMsgObjects = getUnsendMessagesObjects(msgsToDelete); + + // sending to recipient all the messages separately for now + await Promise.all( + unsendMsgObjects.map(unsendObject => + getMessageQueue() + .sendSyncMessage(unsendObject) + .catch(window?.log?.error) + ) ); + await deleteMessagesFromSwarmAndCompletelyLocally(conversation, msgsToDelete); + // Update view and trigger update window.inboxStore?.dispatch(resetSelectedMessageIds()); - if (deleteResult) { - ToastUtils.pushDeleted(); - } else { - ToastUtils.someDeletionsFailed(); - } + ToastUtils.pushDeleted(); } const doDeleteSelectedMessagesInSOGS = async ( @@ -233,11 +250,15 @@ const doDeleteSelectedMessagesInSOGS = async ( * * It does what needs to be done on a user action to delete messages for each conversation type */ -const doDeleteSelectedMessages = async ( - selectedMessages: Array, - conversation: ConversationModel, - shouldDeleteForEveryone: boolean -) => { +const doDeleteSelectedMessages = async ({ + conversation, + selectedMessages, + deleteForEveryone, +}: { + selectedMessages: Array; + conversation: ConversationModel; + deleteForEveryone: boolean; +}) => { const ourDevicePubkey = UserUtils.getOurPubKeyStrFromCache(); if (!ourDevicePubkey) { return; @@ -249,16 +270,16 @@ const doDeleteSelectedMessages = async ( } //#region deletion for 1-1 and closed groups - if (!isAllOurs) { - ToastUtils.pushMessageDeleteForbidden(); - window.inboxStore?.dispatch(resetSelectedMessageIds()); - return; - } - if (shouldDeleteForEveryone) { + if (deleteForEveryone) { + if (!isAllOurs) { + ToastUtils.pushMessageDeleteForbidden(); + window.inboxStore?.dispatch(resetSelectedMessageIds()); + return; + } return unsendMessagesForEveryone(conversation, selectedMessages); } - return deleteJustForThisUser(conversation, selectedMessages); + return unsendMessageJustForThisUser(conversation, selectedMessages); //#endregion }; @@ -284,7 +305,7 @@ export async function deleteMessagesByIdForEveryone( okText: window.i18n('deleteForEveryone'), okTheme: SessionButtonColor.Danger, onClickOk: async () => { - await doDeleteSelectedMessages(selectedMessages, conversation, true); + await doDeleteSelectedMessages({ selectedMessages, conversation, deleteForEveryone: true }); // explicity close modal for this case. window.inboxStore?.dispatch(updateConfirmModal(null)); @@ -295,7 +316,6 @@ export async function deleteMessagesByIdForEveryone( ); } -// tslint:disable-next-line: max-func-body-length export async function deleteMessagesById(messageIds: Array, conversationId: string) { const conversation = getConversationController().getOrThrow(conversationId); const selectedMessages = _.compact( @@ -313,7 +333,12 @@ export async function deleteMessagesById(messageIds: Array, conversation okText: window.i18n('delete'), okTheme: SessionButtonColor.Danger, onClickOk: async () => { - await doDeleteSelectedMessages(selectedMessages, conversation, false); + await doDeleteSelectedMessages({ + selectedMessages, + conversation, + deleteForEveryone: false, + }); + window.inboxStore?.dispatch(updateConfirmModal(null)); }, closeAfterInput: false, }) diff --git a/ts/receiver/contentMessage.ts b/ts/receiver/contentMessage.ts index 73ce9ae2b..a00d67fcc 100644 --- a/ts/receiver/contentMessage.ts +++ b/ts/receiver/contentMessage.ts @@ -20,8 +20,8 @@ import { getAllCachedECKeyPair } from './closedGroups'; import { getMessageBySenderAndTimestamp } from '../data/data'; import { handleCallMessage } from './callMessage'; import { - deleteMessageLocallyOnly, - deleteMessagesFromSwarmOnly, + deleteMessagesFromSwarmAndCompletelyLocally, + deleteMessagesFromSwarmAndMarkAsDeletedLocally, } from '../interactions/conversations/unsendingInteractions'; export async function handleContentMessage(envelope: EnvelopePlus, messageHash?: string) { @@ -499,21 +499,16 @@ async function handleTypingMessage( * @param unsendMessage data required to delete message */ async function handleUnsendMessage(envelope: EnvelopePlus, unsendMessage: SignalService.Unsend) { - const { source: unsendSource } = envelope; const { author: messageAuthor, timestamp } = unsendMessage; - await removeFromCache(envelope); //#region early exit conditions - if (!unsendMessage || !unsendSource) { - window?.log?.error('UnsendMessageHandler:: Invalid parameters -- dropping message.'); + if (!unsendMessage) { + window?.log?.error('handleUnsendMessage: Invalid parameters -- dropping message.'); } if (!timestamp) { - window?.log?.error('UnsendMessageHander:: Invalid timestamp -- dropping message'); - } - const conversation = getConversationController().get(unsendSource); - if (!conversation) { - return; + window?.log?.error('handleUnsendMessage: Invalid timestamp -- dropping message'); } + const messageToDelete = await getMessageBySenderAndTimestamp({ source: messageAuthor, timestamp: Lodash.toNumber(timestamp), @@ -523,21 +518,22 @@ async function handleUnsendMessage(envelope: EnvelopePlus, unsendMessage: Signal //#region executing deletion if (messageHash && messageToDelete) { - const wasDeleted = await deleteMessagesFromSwarmOnly([messageToDelete]); - if (!wasDeleted) { - window.log.warn( - 'handleUnsendMessage: got a request to delete ', - messageHash, - ' but an error happened during deleting it from our swarm.' - ); + window.log.info('handleUnsendMessage: got a request to delete ', messageHash); + const conversation = getConversationController().get(messageToDelete.get('conversationId')); + if (!conversation) { + await removeFromCache(envelope); + + return; + } + if (messageToDelete.getSource() === UserUtils.getOurPubKeyStrFromCache()) { + // a message we sent is completely removed when we get a unsend request + void deleteMessagesFromSwarmAndCompletelyLocally(conversation, [messageToDelete]); + } else { + void deleteMessagesFromSwarmAndMarkAsDeletedLocally(conversation, [messageToDelete]); } - // still, delete it locally - await deleteMessageLocallyOnly({ - conversation, - message: messageToDelete, - deletionType: 'markDeleted', - }); } + await removeFromCache(envelope); + //#endregion } diff --git a/ts/session/sending/MessageQueue.ts b/ts/session/sending/MessageQueue.ts index d1a248f9a..0ecca332e 100644 --- a/ts/session/sending/MessageQueue.ts +++ b/ts/session/sending/MessageQueue.ts @@ -114,7 +114,11 @@ export class MessageQueue { if (!message) { return; } - if (!(message instanceof ConfigurationMessage) && !(message as any)?.syncTarget) { + if ( + !(message instanceof ConfigurationMessage) && + !(message instanceof UnsendMessage) && + !(message as any)?.syncTarget + ) { throw new Error('Invalid message given to sendSyncMessage'); } @@ -226,6 +230,7 @@ export class MessageQueue { if ( message instanceof ConfigurationMessage || message instanceof ClosedGroupNewMessage || + message instanceof UnsendMessage || (message as any).syncTarget?.length > 0 ) { window?.log?.warn('Processing sync message'); diff --git a/ts/session/utils/syncUtils.ts b/ts/session/utils/syncUtils.ts index c4cac9648..1ba1b1392 100644 --- a/ts/session/utils/syncUtils.ts +++ b/ts/session/utils/syncUtils.ts @@ -27,6 +27,7 @@ import { ExpirationTimerUpdateMessage } from '../messages/outgoing/controlMessag import { getV2OpenGroupRoom } from '../../data/opengroups'; import { getCompleteUrlFromRoom } from '../../opengroup/utils/OpenGroupUtils'; import { DURATION } from '../constants'; +import { UnsendMessage } from '../messages/outgoing/controlMessage/UnsendMessage'; const ITEM_ID_LAST_SYNC_TIMESTAMP = 'lastSyncedTimestamp'; @@ -295,7 +296,11 @@ const buildSyncExpireTimerMessage = ( }); }; -export type SyncMessageType = VisibleMessage | ExpirationTimerUpdateMessage | ConfigurationMessage; +export type SyncMessageType = + | VisibleMessage + | ExpirationTimerUpdateMessage + | ConfigurationMessage + | UnsendMessage; export const buildSyncMessage = ( identifier: string,