diff --git a/ts/ConversationController.ts b/ts/ConversationController.ts index d678b3a47a..c414ae2aa7 100644 --- a/ts/ConversationController.ts +++ b/ts/ConversationController.ts @@ -39,6 +39,7 @@ import * as StorageService from './services/storage'; import type { ConversationPropsForUnreadStats } from './util/countUnreadStats'; import { countAllConversationsUnreadStats } from './util/countUnreadStats'; import { isTestOrMockEnvironment } from './environment'; +import { conversationJobQueue } from './jobs/conversationJobQueue'; type ConvoMatchType = | { @@ -1532,4 +1533,45 @@ export class ConversationController { throw error; } } + async archiveSessionsForConversation( + conversationId: string | undefined + ): Promise { + const conversation = window.ConversationController.get(conversationId); + if (!conversation) { + return; + } + + const logId = `archiveSessionsForConversation/${conversation.idForLogging()}`; + + log.info(`${logId}: Starting. First archiving sessions...`); + const recipients = conversation.getRecipients(); + const queue = new PQueue({ concurrency: 1 }); + recipients.forEach(serviceId => { + drop( + queue.add(async () => { + await window.textsecure.storage.protocol.archiveAllSessions( + serviceId + ); + }) + ); + }); + await queue.onEmpty(); + + if (conversation.get('senderKeyInfo')) { + log.info(`${logId}: Next, clearing senderKeyInfo...`); + conversation.set({ senderKeyInfo: undefined }); + await DataWriter.updateConversation(conversation.attributes); + } + + log.info(`${logId}: Now queuing null message send...`); + const job = await conversationJobQueue.add({ + type: 'NullMessage', + conversationId: conversation.id, + }); + + log.info(`${logId}: Send queued; waiting for send completion...`); + await job.completion; + + log.info(`${logId}: Complete!`); + } } diff --git a/ts/jobs/helpers/sendNullMessage.ts b/ts/jobs/helpers/sendNullMessage.ts index b6a3f8832f..2c1b3f0cbc 100644 --- a/ts/jobs/helpers/sendNullMessage.ts +++ b/ts/jobs/helpers/sendNullMessage.ts @@ -22,6 +22,7 @@ import { UnregisteredUserError, } from '../../textsecure/Errors'; import MessageSender from '../../textsecure/SendMessage'; +import { sendToGroup } from '../../util/sendToGroup'; async function clearResetsTracking(idForTracking: string | undefined) { if (!idForTracking) { @@ -65,39 +66,76 @@ export async function sendNullMessage( const contentHint = ContentHint.RESENDABLE; const sendType = 'nullMessage'; - if (!isDirectConversation(conversation.attributes)) { - log.info('Failing attempt to send null message to group'); - return; - } - // Note: we will send to blocked users, to those still in message request state, etc. // Any needed blocking should still apply once the decryption error is fixed. - if (isConversationUnregistered(conversation.attributes)) { - await clearResetsTracking(idForTracking); - log.info( - `conversation ${conversation.idForLogging()} is unregistered; refusing to send null message` - ); - return; - } - try { const proto = MessageSender.getNullMessage(); - - await handleMessageSend( - messaging.sendIndividualProto({ - contentHint, - serviceId: conversation.getSendTarget(), - options: sendOptions, - proto, - timestamp, - urgent: false, - }), - { - messageIds: [], - sendType, + if (isDirectConversation(conversation.attributes)) { + if (isConversationUnregistered(conversation.attributes)) { + await clearResetsTracking(idForTracking); + log.info( + `conversation ${conversation.idForLogging()} is unregistered; refusing to send null message` + ); + return; } - ); + + await conversation.queueJob( + 'conversationQueue/sendNullMessage/direct', + _abortSignal => + handleMessageSend( + messaging.sendIndividualProto({ + contentHint, + serviceId: conversation.getSendTarget(), + options: sendOptions, + proto, + timestamp, + urgent: false, + }), + { + messageIds: [], + sendType, + } + ) + ); + } else { + const groupV2Info = conversation.getGroupV2Info(); + if (groupV2Info) { + groupV2Info.revision = 0; + } + + await conversation.queueJob( + 'conversationQueue/sendNullMessage/group', + abortSignal => + sendToGroup({ + abortSignal, + contentHint: ContentHint.RESENDABLE, + groupSendOptions: { + attachments: [], + bodyRanges: [], + contact: [], + deletedForEveryoneTimestamp: undefined, + expireTimer: undefined, + groupV2: groupV2Info, + messageText: undefined, + preview: [], + profileKey: undefined, + quote: undefined, + sticker: undefined, + storyContext: undefined, + reaction: undefined, + targetTimestampForEdit: undefined, + timestamp, + }, + messageId: undefined, + sendOptions, + sendTarget: conversation.toSenderKeyTarget(), + sendType, + story: false, + urgent: true, + }) + ); + } } catch (error: unknown) { if ( error instanceof OutgoingIdentityKeyError || diff --git a/ts/windows/main/start.ts b/ts/windows/main/start.ts index 2e48ebb626..9a21e3def9 100644 --- a/ts/windows/main/start.ts +++ b/ts/windows/main/start.ts @@ -61,9 +61,16 @@ if ( cdsLookup: (options: CdsLookupOptionsType) => window.textsecure.server?.cdsLookup(options), getSelectedConversation: () => { - return window.ConversationController.get( - window.reduxStore.getState().conversations.selectedConversationId - )?.attributes; + const conversationId = + window.reduxStore.getState().conversations.selectedConversationId; + return window.ConversationController.get(conversationId)?.attributes; + }, + archiveSessionsForCurrentConversation: async () => { + const conversationId = + window.reduxStore.getState().conversations.selectedConversationId; + await window.ConversationController.archiveSessionsForConversation( + conversationId + ); }, getConversation: (id: string) => window.ConversationController.get(id), getMessageById: (id: string) => window.MessageCache.getById(id)?.attributes,