diff --git a/ts/groups.ts b/ts/groups.ts index 1c141c0d76..268ed8ac73 100644 --- a/ts/groups.ts +++ b/ts/groups.ts @@ -1316,10 +1316,10 @@ export async function modifyGroupV2({ timestamp, profileKey, }, - conversation, contentHint: ContentHint.RESENDABLE, messageId: undefined, sendOptions, + sendTarget: conversation.toSenderKeyTarget(), sendType: 'groupChange', }), { messageIds: [], sendType: 'groupChange' } @@ -1686,15 +1686,15 @@ export async function createGroupV2({ messageIds: [], send: async () => window.Signal.Util.sendToGroup({ + contentHint: ContentHint.RESENDABLE, groupSendOptions: { groupV2: groupV2Info, timestamp, profileKey, }, - conversation, - contentHint: ContentHint.RESENDABLE, messageId: undefined, sendOptions, + sendTarget: conversation.toSenderKeyTarget(), sendType: 'groupChange', }), sendType: 'groupChange', @@ -2213,6 +2213,7 @@ export async function initiateMigrationToGroupV2( send: async () => // Minimal message to notify group members about migration window.Signal.Util.sendToGroup({ + contentHint: ContentHint.RESENDABLE, groupSendOptions: { groupV2: conversation.getGroupV2Info({ includePendingMembers: true, @@ -2220,10 +2221,9 @@ export async function initiateMigrationToGroupV2( timestamp, profileKey: ourProfileKey, }, - conversation, - contentHint: ContentHint.RESENDABLE, messageId: undefined, sendOptions, + sendTarget: conversation.toSenderKeyTarget(), sendType: 'groupChange', }), sendType: 'groupChange', diff --git a/ts/jobs/normalMessageSendJobQueue.ts b/ts/jobs/normalMessageSendJobQueue.ts index 290ee6d7e2..a255bcf479 100644 --- a/ts/jobs/normalMessageSendJobQueue.ts +++ b/ts/jobs/normalMessageSendJobQueue.ts @@ -214,6 +214,7 @@ export class NormalMessageSendJobQueue extends JobQueue window.Signal.Util.sendToGroup({ + contentHint: ContentHint.RESENDABLE, groupSendOptions: { attachments, deletedForEveryoneTimestamp, @@ -232,10 +233,9 @@ export class NormalMessageSendJobQueue extends JobQueue { } else { log.info('sending group reaction message'); promise = window.Signal.Util.sendToGroup({ + contentHint: ContentHint.RESENDABLE, groupSendOptions: { groupV1: conversation.getGroupV1Info( recipientIdentifiersWithoutMe @@ -213,10 +214,9 @@ export class ReactionJobQueue extends JobQueue { expireTimer, profileKey, }, - conversation, - contentHint: ContentHint.RESENDABLE, messageId, sendOptions, + sendTarget: conversation.toSenderKeyTarget(), sendType: 'reaction', }); } diff --git a/ts/models/conversations.ts b/ts/models/conversations.ts index be6f23a79a..5d4ce9a6de 100644 --- a/ts/models/conversations.ts +++ b/ts/models/conversations.ts @@ -11,6 +11,7 @@ import type { MessageAttributesType, MessageModelCollectionType, QuotedMessageType, + SenderKeyInfoType, VerificationOptions, WhatIsThis, } from '../model-types.d'; @@ -102,6 +103,7 @@ import { createIdenticon } from '../util/createIdenticon'; import * as log from '../logging/log'; import * as Errors from '../types/errors'; import { isMessageUnread } from '../util/isMessageUnread'; +import type { SenderKeyTargetType } from '../util/sendToGroup'; /* eslint-disable more/no-then */ window.Whisper = window.Whisper || {}; @@ -356,6 +358,23 @@ export class ConversationModel extends window.Backbone } } + toSenderKeyTarget(): SenderKeyTargetType { + return { + getGroupId: () => this.get('groupId'), + getMembers: () => this.getMembers(), + hasMember: (id: string) => this.hasMember(id), + idForLogging: () => this.idForLogging(), + isGroupV2: () => isGroupV2(this.attributes), + isValid: () => isGroupV2(this.attributes), + + getSenderKeyInfo: () => this.get('senderKeyInfo'), + saveSenderKeyInfo: async (senderKeyInfo: SenderKeyInfoType) => { + this.set({ senderKeyInfo }); + window.Signal.Data.updateConversation(this.attributes); + }, + }; + } + isMemberRequestingToJoin(id: string): boolean { if (!isGroupV2(this.attributes)) { return false; @@ -1272,11 +1291,11 @@ export class ConversationModel extends window.Backbone window.Signal.Util.sendContentMessageToGroup({ contentHint: ContentHint.IMPLICIT, contentMessage, - conversation: this, messageId: undefined, online: true, recipients: groupMembers, sendOptions, + sendTarget: this.toSenderKeyTarget(), sendType: 'typing', timestamp, }), @@ -3759,6 +3778,7 @@ export class ConversationModel extends window.Backbone } return window.Signal.Util.sendToGroup({ + contentHint: ContentHint.RESENDABLE, groupSendOptions: { groupV1: this.getGroupV1Info(), groupV2: this.getGroupV2Info(), @@ -3766,10 +3786,9 @@ export class ConversationModel extends window.Backbone timestamp, profileKey, }, - conversation: this, - contentHint: ContentHint.RESENDABLE, messageId, sendOptions, + sendTarget: this.toSenderKeyTarget(), sendType: 'deleteForEveryone', }); })(); diff --git a/ts/services/calling.ts b/ts/services/calling.ts index b8cbe9f6d3..e4dd05132f 100644 --- a/ts/services/calling.ts +++ b/ts/services/calling.ts @@ -949,15 +949,15 @@ export class CallingClass { send: () => conversation.queueJob('sendGroupCallUpdateMessage', () => window.Signal.Util.sendToGroup({ + contentHint: ContentHint.DEFAULT, groupSendOptions: { groupCallUpdate: { eraId }, groupV2, timestamp, }, - conversation, - contentHint: ContentHint.DEFAULT, messageId: undefined, sendOptions, + sendTarget: conversation.toSenderKeyTarget(), sendType: 'callingMessage', }) ), @@ -1567,11 +1567,11 @@ export class CallingClass { window.Signal.Util.sendContentMessageToGroup({ contentHint: ContentHint.DEFAULT, contentMessage, - conversation, isPartialSend: false, messageId: undefined, recipients: conversation.getRecipients(), sendOptions: await getSendOptions(conversation.attributes), + sendTarget: conversation.toSenderKeyTarget(), sendType: 'callingMessage', timestamp, }), diff --git a/ts/sql/Client.ts b/ts/sql/Client.ts index 321e8c1034..3073afb7cb 100644 --- a/ts/sql/Client.ts +++ b/ts/sql/Client.ts @@ -288,6 +288,7 @@ const dataInterface: ClientInterface = { _deleteAllStoryDistributions, createNewStoryDistribution, getAllStoryDistributionsWithMembers, + modifyStoryDistribution, modifyStoryDistributionMembers, deleteStoryDistribution, @@ -1660,15 +1661,20 @@ async function _deleteAllStoryDistributions(): Promise { await channels._deleteAllStoryDistributions(); } async function createNewStoryDistribution( - story: StoryDistributionWithMembersType + distribution: StoryDistributionWithMembersType ): Promise { - await channels.createNewStoryDistribution(story); + await channels.createNewStoryDistribution(distribution); } async function getAllStoryDistributionsWithMembers(): Promise< Array > { return channels.getAllStoryDistributionsWithMembers(); } +async function modifyStoryDistribution( + distribution: StoryDistributionType +): Promise { + await channels.modifyStoryDistribution(distribution); +} async function modifyStoryDistributionMembers( id: string, options: { diff --git a/ts/sql/Interface.ts b/ts/sql/Interface.ts index 5918d736cc..74845fec3a 100644 --- a/ts/sql/Interface.ts +++ b/ts/sql/Interface.ts @@ -499,11 +499,12 @@ export type DataInterface = { >; _deleteAllStoryDistributions(): Promise; createNewStoryDistribution( - story: StoryDistributionWithMembersType + distribution: StoryDistributionWithMembersType ): Promise; getAllStoryDistributionsWithMembers(): Promise< Array >; + modifyStoryDistribution(distribution: StoryDistributionType): Promise; modifyStoryDistributionMembers( id: string, options: { diff --git a/ts/sql/Server.ts b/ts/sql/Server.ts index ecc5e197f2..505456ceaa 100644 --- a/ts/sql/Server.ts +++ b/ts/sql/Server.ts @@ -280,6 +280,7 @@ const dataInterface: ServerInterface = { _deleteAllStoryDistributions, createNewStoryDistribution, getAllStoryDistributionsWithMembers, + modifyStoryDistribution, modifyStoryDistributionMembers, deleteStoryDistribution, @@ -3848,12 +3849,12 @@ async function _deleteAllStoryDistributions(): Promise { db.prepare('DELETE FROM storyDistributions;').run(); } async function createNewStoryDistribution( - story: StoryDistributionWithMembersType + distribution: StoryDistributionWithMembersType ): Promise { const db = getInstance(); db.transaction(() => { - const payload = freezeStoryDistribution(story); + const payload = freezeStoryDistribution(distribution); prepare( db, @@ -3874,7 +3875,7 @@ async function createNewStoryDistribution( ` ).run(payload); - const { id: listId, members } = story; + const { id: listId, members } = distribution; const memberInsertStatement = prepare( db, @@ -3910,6 +3911,24 @@ async function getAllStoryDistributionsWithMembers(): Promise< members: (byListId[list.id] || []).map(member => member.uuid), })); } +async function modifyStoryDistribution( + distribution: StoryDistributionType +): Promise { + const payload = freezeStoryDistribution(distribution); + const db = getInstance(); + prepare( + db, + ` + UPDATE storyDistributions + SET + name = $name, + avatarUrlPath = $avatarUrlPath, + avatarKey = $avatarKey, + senderKeyInfoJson = $senderKeyInfoJson + WHERE id = $id + ` + ).run(payload); +} async function modifyStoryDistributionMembers( listId: string, { diff --git a/ts/test-electron/sql/storyDistribution_test.ts b/ts/test-electron/sql/storyDistribution_test.ts index fcd5473b78..3e74b8bc1a 100644 --- a/ts/test-electron/sql/storyDistribution_test.ts +++ b/ts/test-electron/sql/storyDistribution_test.ts @@ -17,6 +17,7 @@ const { createNewStoryDistribution, deleteStoryDistribution, getAllStoryDistributionsWithMembers, + modifyStoryDistribution, modifyStoryDistributionMembers, } = dataInterface; @@ -59,6 +60,55 @@ describe('sql/storyDistribution', () => { assert.lengthOf(await getAllStoryDistributionsWithMembers(), 0); }); + it('updates core fields with modifyStoryDistribution', async () => { + const UUID_1 = getUuid(); + const UUID_2 = getUuid(); + const list: StoryDistributionWithMembersType = { + id: getUuid(), + name: 'My Story', + avatarUrlPath: getUuid(), + avatarKey: getRandomBytes(128), + members: [UUID_1, UUID_2], + senderKeyInfo: { + createdAtDate: Date.now(), + distributionId: getUuid(), + memberDevices: [], + }, + }; + + await createNewStoryDistribution(list); + + assert.lengthOf(await _getAllStoryDistributions(), 1); + assert.lengthOf(await _getAllStoryDistributionMembers(), 2); + + const updated = { + ...list, + name: 'Updated story', + avatarKey: getRandomBytes(128), + avatarUrlPath: getUuid(), + senderKeyInfo: { + createdAtDate: Date.now() + 10, + distributionId: getUuid(), + memberDevices: [ + { + id: 1, + identifier: UUID_1, + registrationId: 232, + }, + ], + }, + }; + + await modifyStoryDistribution(updated); + + assert.lengthOf(await _getAllStoryDistributions(), 1); + assert.lengthOf(await _getAllStoryDistributionMembers(), 2); + + const allHydratedLists = await getAllStoryDistributionsWithMembers(); + assert.lengthOf(allHydratedLists, 1); + assert.deepEqual(allHydratedLists[0], updated); + }); + it('adds and removes with modifyStoryDistributionMembers', async () => { const UUID_1 = getUuid(); const UUID_2 = getUuid(); diff --git a/ts/util/sendToGroup.ts b/ts/util/sendToGroup.ts index 5da692444d..8085a74244 100644 --- a/ts/util/sendToGroup.ts +++ b/ts/util/sendToGroup.ts @@ -34,7 +34,10 @@ import { IdentityKeys, SenderKeys, Sessions } from '../LibSignalStores'; import type { ConversationModel } from '../models/conversations'; import type { DeviceType, CallbackResultType } from '../textsecure/Types.d'; import { getKeysForIdentifier } from '../textsecure/getKeysForIdentifier'; -import type { ConversationAttributesType } from '../model-types.d'; +import type { + ConversationAttributesType, + SenderKeyInfoType, +} from '../model-types.d'; import type { SendTypesType } from './handleMessageSend'; import { handleMessageSend, @@ -51,7 +54,6 @@ import { SignalService as Proto } from '../protobuf'; import * as RemoteConfig from '../RemoteConfig'; import { strictAssert } from './assert'; -import { isGroupV2 } from './whatTypeOfConversation'; import * as log from '../logging/log'; const ERROR_EXPIRED_OR_MISSING_DEVICES = 409; @@ -70,21 +72,33 @@ const ZERO_ACCESS_KEY = Bytes.toBase64(new Uint8Array(ACCESS_KEY_LENGTH)); // Public API: +export type SenderKeyTargetType = { + getGroupId: () => string | undefined; + getMembers: () => Array; + hasMember: (id: string) => boolean; + idForLogging: () => string; + isGroupV2: () => boolean; + isValid: () => boolean; + + getSenderKeyInfo: () => SenderKeyInfoType | undefined; + saveSenderKeyInfo: (senderKeyInfo: SenderKeyInfoType) => Promise; +}; + export async function sendToGroup({ contentHint, - conversation, groupSendOptions, - messageId, isPartialSend, + messageId, sendOptions, + sendTarget, sendType, }: { contentHint: number; - conversation: ConversationModel; groupSendOptions: GroupSendOptionsType; isPartialSend?: boolean; messageId: string | undefined; sendOptions?: SendOptionsType; + sendTarget: SenderKeyTargetType; sendType: SendTypesType; }): Promise { strictAssert( @@ -105,11 +119,11 @@ export async function sendToGroup({ return sendContentMessageToGroup({ contentHint, contentMessage, - conversation, isPartialSend, messageId, recipients, sendOptions, + sendTarget, sendType, timestamp, }); @@ -118,27 +132,27 @@ export async function sendToGroup({ export async function sendContentMessageToGroup({ contentHint, contentMessage, - conversation, isPartialSend, messageId, online, recipients, sendOptions, + sendTarget, sendType, timestamp, }: { contentHint: number; contentMessage: Proto.Content; - conversation: ConversationModel; isPartialSend?: boolean; messageId: string | undefined; online?: boolean; recipients: Array; sendOptions?: SendOptionsType; + sendTarget: SenderKeyTargetType; sendType: SendTypesType; timestamp: number; }): Promise { - const logId = conversation.idForLogging(); + const logId = sendTarget.idForLogging(); strictAssert( window.textsecure.messaging, 'sendContentMessageToGroup: textsecure.messaging not available!' @@ -152,19 +166,19 @@ export async function sendContentMessageToGroup({ isEnabled('desktop.sendSenderKey3') && ourConversation?.get('capabilities')?.senderKey && RemoteConfig.isEnabled('desktop.senderKey.send') && - isGroupV2(conversation.attributes) + sendTarget.isValid() ) { try { return await sendToGroupViaSenderKey({ contentHint, contentMessage, - conversation, isPartialSend, messageId, online, recipients, recursionCount: 0, sendOptions, + sendTarget, sendType, timestamp, }); @@ -194,9 +208,7 @@ export async function sendContentMessageToGroup({ sendType, timestamp, }); - const groupId = isGroupV2(conversation.attributes) - ? conversation.get('groupId') - : undefined; + const groupId = sendTarget.isGroupV2() ? sendTarget.getGroupId() : undefined; return window.textsecure.messaging.sendGroupProto({ contentHint, groupId, @@ -213,32 +225,32 @@ export async function sendContentMessageToGroup({ export async function sendToGroupViaSenderKey(options: { contentHint: number; contentMessage: Proto.Content; - conversation: ConversationModel; isPartialSend?: boolean; messageId: string | undefined; online?: boolean; recipients: Array; recursionCount: number; sendOptions?: SendOptionsType; + sendTarget: SenderKeyTargetType; sendType: SendTypesType; timestamp: number; }): Promise { const { contentHint, contentMessage, - conversation, isPartialSend, messageId, online, - recursionCount, recipients, + recursionCount, sendOptions, + sendTarget, sendType, timestamp, } = options; const { ContentHint } = Proto.UnidentifiedSenderMessage.Message; - const logId = conversation.idForLogging(); + const logId = sendTarget.idForLogging(); log.info( `sendToGroupViaSenderKey/${logId}: Starting ${timestamp}, recursion count ${recursionCount}...` ); @@ -249,10 +261,10 @@ export async function sendToGroupViaSenderKey(options: { ); } - const groupId = conversation.get('groupId'); - if (!groupId || !isGroupV2(conversation.attributes)) { + const groupId = sendTarget.getGroupId(); + if (!sendTarget.isValid()) { throw new Error( - `sendToGroupViaSenderKey/${logId}: Missing groupId or group is not GV2` + `sendToGroupViaSenderKey/${logId}: sendTarget is not valid!` ); } @@ -271,29 +283,40 @@ export async function sendToGroupViaSenderKey(options: { 'sendToGroupViaSenderKey: textsecure.messaging not available!' ); - const { attributes }: { attributes: ConversationAttributesType } = - conversation; - // 1. Add sender key info if we have none, or clear out if it's too old const THIRTY_DAYS = 30 * DAY; - if (!attributes.senderKeyInfo) { + + // Note: From here on, generally need to recurse if we change senderKeyInfo + const senderKeyInfo = sendTarget.getSenderKeyInfo(); + + if (!senderKeyInfo) { log.info( `sendToGroupViaSenderKey/${logId}: Adding initial sender key info` ); - conversation.set({ - senderKeyInfo: { - createdAtDate: Date.now(), - distributionId: UUID.generate().toString(), - memberDevices: [], - }, + await sendTarget.saveSenderKeyInfo({ + createdAtDate: Date.now(), + distributionId: UUID.generate().toString(), + memberDevices: [], }); - window.Signal.Data.updateConversation(attributes); - } else if (isOlderThan(attributes.senderKeyInfo.createdAtDate, THIRTY_DAYS)) { - const { createdAtDate } = attributes.senderKeyInfo; + + // Restart here because we updated senderKeyInfo + return sendToGroupViaSenderKey({ + ...options, + recursionCount: recursionCount + 1, + }); + } + if (isOlderThan(senderKeyInfo.createdAtDate, THIRTY_DAYS)) { + const { createdAtDate } = senderKeyInfo; log.info( `sendToGroupViaSenderKey/${logId}: Resetting sender key; ${createdAtDate} is too old` ); - await resetSenderKey(conversation); + await resetSenderKey(sendTarget); + + // Restart here because we updated senderKeyInfo + return sendToGroupViaSenderKey({ + ...options, + recursionCount: recursionCount + 1, + }); } // 2. Fetch all devices we believe we'll be sending to @@ -320,15 +343,8 @@ export async function sendToGroupViaSenderKey(options: { }); } - strictAssert( - attributes.senderKeyInfo, - `sendToGroupViaSenderKey/${logId}: expect senderKeyInfo` - ); - // Note: From here on, we will need to recurse if we change senderKeyInfo - const { memberDevices, distributionId, createdAtDate } = - attributes.senderKeyInfo; - - const memberSet = new Set(conversation.getMembers()); + const { memberDevices, distributionId, createdAtDate } = senderKeyInfo; + const memberSet = new Set(sendTarget.getMembers()); // 4. Partition devices into sender key and non-sender key groups const [devicesForSenderKey, devicesForNormalSend] = partition( @@ -366,10 +382,10 @@ export async function sendToGroupViaSenderKey(options: { // 7. If members have been removed from the group, we need to reset our sender key, then // start over to get a fresh set of target devices. const keyNeedsReset = Array.from(removedFromMemberUuids).some( - uuid => !conversation.hasMember(uuid) + uuid => !sendTarget.hasMember(uuid) ); if (keyNeedsReset) { - await resetSenderKey(conversation); + await resetSenderKey(sendTarget); // Restart here to start over; empty memberDevices means we'll send distribution // message to everyone. @@ -403,14 +419,11 @@ export async function sendToGroupViaSenderKey(options: { // Update memberDevices with new devices const updatedMemberDevices = [...memberDevices, ...newToMemberDevices]; - conversation.set({ - senderKeyInfo: { - createdAtDate, - distributionId, - memberDevices: updatedMemberDevices, - }, + await sendTarget.saveSenderKeyInfo({ + createdAtDate, + distributionId, + memberDevices: updatedMemberDevices, }); - window.Signal.Data.updateConversation(conversation.attributes); // Restart here because we might have discovered new or dropped devices as part of // distributing our sender key. @@ -430,14 +443,14 @@ export async function sendToGroupViaSenderKey(options: { ), ]; - conversation.set({ - senderKeyInfo: { - createdAtDate, - distributionId, - memberDevices: updatedMemberDevices, - }, + await sendTarget.saveSenderKeyInfo({ + createdAtDate, + distributionId, + memberDevices: updatedMemberDevices, }); - window.Signal.Data.updateConversation(conversation.attributes); + + // Note, we do not need to restart here because we don't refer back to senderKeyInfo + // after this point. } // 10. Send the Sender Key message! @@ -513,7 +526,7 @@ export async function sendToGroupViaSenderKey(options: { }); } if (error.code === ERROR_STALE_DEVICES) { - await handle410Response(conversation, error); + await handle410Response(sendTarget, error); // Restart here to use the right registrationIds for devices we already knew about, // as well as send our sender key to these re-registered or re-linked devices. @@ -591,7 +604,7 @@ export async function sendToGroupViaSenderKey(options: { const recipientUuid = sentToConversation.get('uuid'); if (!recipientUuid) { log.warn( - `sendToGroupViaSenderKey/callback: Conversation ${conversation.idForLogging()} had no UUID` + `sendToGroupViaSenderKey/callback: Conversation ${sentToConversation.idForLogging()} had no UUID` ); return; } @@ -730,10 +743,10 @@ async function handle409Response(logId: string, error: HTTPError) { } async function handle410Response( - conversation: ConversationModel, + sendTarget: SenderKeyTargetType, error: HTTPError ) { - const logId = conversation.idForLogging(); + const logId = sendTarget.idForLogging(); const parsed = multiRecipient410ResponseSchema.safeParse(error.response); if (parsed.success) { @@ -757,21 +770,18 @@ async function handle410Response( // Forget that we've sent our sender key to these devices, since they've // been re-registered or re-linked. - const senderKeyInfo = conversation.get('senderKeyInfo'); + const senderKeyInfo = sendTarget.getSenderKeyInfo(); if (senderKeyInfo) { const devicesToRemove: Array = devices.staleDevices.map(id => ({ id, identifier: uuid })); - conversation.set({ - senderKeyInfo: { - ...senderKeyInfo, - memberDevices: differenceWith( - senderKeyInfo.memberDevices, - devicesToRemove, - partialDeviceComparator - ), - }, + await sendTarget.saveSenderKeyInfo({ + ...senderKeyInfo, + memberDevices: differenceWith( + senderKeyInfo.memberDevices, + devicesToRemove, + partialDeviceComparator + ), }); - window.Signal.Data.updateConversation(conversation.attributes); } } }), @@ -836,7 +846,7 @@ async function encryptForSenderKey({ contentMessage: Uint8Array; devices: Array; distributionId: string; - groupId: string; + groupId?: string; }): Promise { const ourUuid = window.textsecure.storage.user.getCheckedUuid(); const ourDeviceId = window.textsecure.storage.user.getDeviceId(); @@ -860,7 +870,7 @@ async function encryptForSenderKey({ () => groupEncrypt(sender, distributionId, senderKeyStore, message) ); - const groupIdBuffer = Buffer.from(groupId, 'base64'); + const groupIdBuffer = groupId ? Buffer.from(groupId, 'base64') : null; const senderCertificateObject = await senderCertificateService.get( SenderCertificateMode.WithoutE164 ); @@ -1027,13 +1037,11 @@ function getOurAddress(): Address { return new Address(ourUuid, ourDeviceId); } -async function resetSenderKey(conversation: ConversationModel): Promise { - const logId = conversation.idForLogging(); +async function resetSenderKey(sendTarget: SenderKeyTargetType): Promise { + const logId = sendTarget.idForLogging(); log.info(`resetSenderKey/${logId}: Sender key needs reset. Clearing data...`); - const { attributes }: { attributes: ConversationAttributesType } = - conversation; - const { senderKeyInfo } = attributes; + const senderKeyInfo = sendTarget.getSenderKeyInfo(); if (!senderKeyInfo) { log.warn(`resetSenderKey/${logId}: No sender key info`); return; @@ -1043,14 +1051,11 @@ async function resetSenderKey(conversation: ConversationModel): Promise { const ourAddress = getOurAddress(); // Note: We preserve existing distributionId to minimize space for sender key storage - conversation.set({ - senderKeyInfo: { - createdAtDate: Date.now(), - distributionId, - memberDevices: [], - }, + await sendTarget.saveSenderKeyInfo({ + createdAtDate: Date.now(), + distributionId, + memberDevices: [], }); - window.Signal.Data.updateConversation(conversation.attributes); const ourUuid = window.storage.user.getCheckedUuid(); await window.textsecure.storage.protocol.removeSenderKey(