diff --git a/ts/receiver/contentMessage.ts b/ts/receiver/contentMessage.ts index 515a8c79a..32dc35540 100644 --- a/ts/receiver/contentMessage.ts +++ b/ts/receiver/contentMessage.ts @@ -30,7 +30,10 @@ import { assertUnreachable } from '../types/sqlSharedTypes'; import { BlockedNumberController } from '../util'; import { ReadReceipts } from '../util/readReceipts'; import { Storage } from '../util/storage'; -import { ContactsWrapperActions } from '../webworker/workers/browser/libsession_worker_interface'; +import { + ContactsWrapperActions, + MetaGroupWrapperActions, +} from '../webworker/workers/browser/libsession_worker_interface'; import { handleCallMessage } from './callMessage'; import { getAllCachedECKeyPair, sentAtMoreRecentThanWrapper } from './closedGroups'; import { ECKeyPair } from './keypairs'; @@ -401,6 +404,37 @@ function shouldDropBlockedUserMessage( return !isControlDataMessageOnly; } +async function dropIncomingGroupMessage(envelope: EnvelopePlus, sentAtTimestamp: number) { + try { + if (PubKey.is03Pubkey(envelope.source)) { + const infos = await MetaGroupWrapperActions.infoGet(envelope.source); + + if (!infos) { + return false; + } + + if ( + sentAtTimestamp && + ((infos.deleteAttachBeforeSeconds && + sentAtTimestamp <= infos.deleteAttachBeforeSeconds * 1000) || + (infos.deleteBeforeSeconds && sentAtTimestamp <= infos.deleteBeforeSeconds * 1000)) + ) { + window?.log?.info( + `Incoming message sent before the group ${ed25519Str(envelope.source)} deleteBeforeSeconds or deleteAttachBeforeSeconds. Dropping it.` + ); + await IncomingMessageCache.removeFromCache(envelope); + return true; + } + } + } catch (e) { + window?.log?.warn( + `dropIncomingGroupMessage failed for group ${ed25519Str(envelope.source)} with `, + e.message + ); + } + return false; +} + export async function innerHandleSwarmContentMessage({ contentDecrypted, envelope, @@ -439,6 +473,11 @@ export async function innerHandleSwarmContentMessage({ window?.log?.info('Allowing group-control message only from blocked user'); } + if (await dropIncomingGroupMessage(envelope, sentAtTimestamp)) { + // message removed from cache in `dropIncomingGroupMessage` already + return; + } + // if this is a direct message, envelope.senderIdentity is undefined // if this is a closed group message, envelope.senderIdentity is the sender's pubkey and envelope.source is the closed group's pubkey const isPrivateConversationMessage = !envelope.senderIdentity; diff --git a/ts/session/apis/snode_api/swarm_polling_config/SwarmPollingGroupConfig.ts b/ts/session/apis/snode_api/swarm_polling_config/SwarmPollingGroupConfig.ts index f3fb572db..bf68a451c 100644 --- a/ts/session/apis/snode_api/swarm_polling_config/SwarmPollingGroupConfig.ts +++ b/ts/session/apis/snode_api/swarm_polling_config/SwarmPollingGroupConfig.ts @@ -10,13 +10,26 @@ import { LibSessionUtil } from '../../../utils/libsession/libsession_utils'; import { SnodeNamespaces } from '../namespaces'; import { RetrieveMessageItemWithNamespace } from '../types'; +/** + * This is a basic optimization to avoid running the logic when the `deleteBeforeSeconds` + * and the `deleteAttachBeforeSeconds` does not change between each polls. + * Essentially, when the `deleteBeforeSeconds` is set in the group info config, + * - on start that map will be empty so we will run the logic to delete any messages sent before that. + * - after each poll, we will only rerun the logic if the new `deleteBeforeSeconds` is higher than the current setting. + * + */ +const lastAppliedRemoveMsgSentBeforeSeconds = new Map(); +const lastAppliedRemoveAttachmentSentBeforeSeconds = new Map(); + async function handleMetaMergeResults(groupPk: GroupPubkeyType) { const infos = await MetaGroupWrapperActions.infoGet(groupPk); if ( infos && isNumber(infos.deleteBeforeSeconds) && isFinite(infos.deleteBeforeSeconds) && - infos.deleteBeforeSeconds > 0 + infos.deleteBeforeSeconds > 0 && + (lastAppliedRemoveMsgSentBeforeSeconds.get(groupPk) || Number.MAX_SAFE_INTEGER) > + infos.deleteBeforeSeconds ) { // delete any messages in this conversation sent before that timestamp (in seconds) const deletedMsgIds = await Data.removeAllMessagesInConversationSentBefore({ @@ -27,16 +40,19 @@ async function handleMetaMergeResults(groupPk: GroupPubkeyType) { `removeAllMessagesInConversationSentBefore of ${ed25519Str(groupPk)} before ${infos.deleteBeforeSeconds}: `, deletedMsgIds ); - await window.inboxStore.dispatch( + window.inboxStore.dispatch( messagesExpired(deletedMsgIds.map(messageId => ({ conversationKey: groupPk, messageId }))) ); + lastAppliedRemoveMsgSentBeforeSeconds.set(groupPk, infos.deleteBeforeSeconds); } if ( infos && isNumber(infos.deleteAttachBeforeSeconds) && isFinite(infos.deleteAttachBeforeSeconds) && - infos.deleteAttachBeforeSeconds > 0 + infos.deleteAttachBeforeSeconds > 0 && + (lastAppliedRemoveAttachmentSentBeforeSeconds.get(groupPk) || Number.MAX_SAFE_INTEGER) > + infos.deleteAttachBeforeSeconds ) { // delete any attachments in this conversation sent before that timestamp (in seconds) const impactedMsgModels = await Data.getAllMessagesWithAttachmentsInConversationSentBefore({ @@ -55,6 +71,7 @@ async function handleMetaMergeResults(groupPk: GroupPubkeyType) { // eslint-disable-next-line no-await-in-loop await msg?.cleanup(); } + lastAppliedRemoveAttachmentSentBeforeSeconds.set(groupPk, infos.deleteAttachBeforeSeconds); } } diff --git a/ts/session/utils/AttachmentsDownload.ts b/ts/session/utils/AttachmentsDownload.ts index d9d54a2ac..82d017f7e 100644 --- a/ts/session/utils/AttachmentsDownload.ts +++ b/ts/session/utils/AttachmentsDownload.ts @@ -2,14 +2,16 @@ import { filter, isNumber, omit } from 'lodash'; import { v4 as uuidv4 } from 'uuid'; -import * as Constants from '../constants'; import { Data } from '../../data/data'; import { MessageModel } from '../../models/message'; import { downloadAttachment, downloadAttachmentSogsV3 } from '../../receiver/attachments'; import { initializeAttachmentLogic, processNewAttachment } from '../../types/MessageAttachment'; import { getAttachmentMetadata } from '../../types/message/initializeAttachmentMetadata'; -import { was404Error } from '../apis/snode_api/onions'; import { AttachmentDownloadMessageDetails } from '../../types/sqlSharedTypes'; +import { MetaGroupWrapperActions } from '../../webworker/workers/browser/libsession_worker_interface'; +import { was404Error } from '../apis/snode_api/onions'; +import * as Constants from '../constants'; +import { PubKey } from '../types'; // this may cause issues if we increment that value to > 1, but only having one job will block the whole queue while one attachment is downloading const MAX_ATTACHMENT_JOB_PARALLELISM = 3; @@ -137,6 +139,34 @@ async function _maybeStartJob() { } } +async function shouldSkipGroupAttachmentDownload({ + groupPk, + messageModel, +}: { + groupPk: string; + messageModel: MessageModel; +}) { + if (!PubKey.is03Pubkey(groupPk)) { + return false; + } + try { + const infos = await MetaGroupWrapperActions.infoGet(groupPk); + const sentAt = messageModel.get('sent_at'); + if (!sentAt) { + return false; + } + if ( + (infos.deleteAttachBeforeSeconds && sentAt <= infos.deleteAttachBeforeSeconds * 1000) || + (infos.deleteBeforeSeconds && sentAt <= infos.deleteBeforeSeconds * 1000) + ) { + return true; + } + } catch (e) { + window.log.warn('shouldSkipGroupAttachmentDownload failed with ', e.message); + } + return false; // try to download it +} + async function _runJob(job: any) { const { id, messageId, attachment, type, index, attempts, isOpenGroupV2, openGroupV2Details } = job || {}; @@ -152,6 +182,17 @@ async function _runJob(job: any) { await _finishJob(null, id); return; } + const shouldSkipJobForGroup = await shouldSkipGroupAttachmentDownload({ + groupPk: found.get('conversationId'), + messageModel: found, + }); + + if (shouldSkipJobForGroup) { + logger.info('_runJob: shouldSkipGroupAttachmentDownload is true, deleting job'); + await _finishJob(null, id); + return; + } + const isTrusted = found.isTrustedForAttachmentDownload(); if (!isTrusted) {