diff --git a/ts/services/backups/export.ts b/ts/services/backups/export.ts index 74faf15a02..4bdba46f2c 100644 --- a/ts/services/backups/export.ts +++ b/ts/services/backups/export.ts @@ -33,6 +33,7 @@ import { explodePromise } from '../../util/explodePromise'; import { isDirectConversation, isGroup, + isGroupV1, isGroupV2, isMe, } from '../../util/whatTypeOfConversation'; @@ -307,6 +308,11 @@ export class BackupExportStream extends Readable { window.storage.get('pinnedConversationIds') || []; for (const { attributes } of window.ConversationController.getAll()) { + if (isGroupV1(attributes)) { + log.warn('backups: skipping gv1 conversation'); + continue; + } + const recipientId = this.getRecipientId(attributes); let pinnedOrder: number | null = null; @@ -773,6 +779,15 @@ export class BackupExportStream extends Readable { message: MessageAttributesType, { aboutMe, callHistoryByCallId, backupLevel }: ToChatItemOptionsType ): Promise { + const conversation = window.ConversationController.get( + message.conversationId + ); + + if (conversation && isGroupV1(conversation.attributes)) { + log.warn('backups: skipping gv1 message'); + return undefined; + } + const chatId = this.getRecipientId({ id: message.conversationId }); if (chatId === undefined) { log.warn('backups: message chat not found'); @@ -1324,9 +1339,21 @@ export class BackupExportStream extends Readable { return { kind: NonBubbleResultKind.Drop }; } + // Create a GV2 tombstone for a deprecated GV1 notification if (isGroupUpdate(message)) { - // GV1 is deprecated. - return { kind: NonBubbleResultKind.Drop }; + updateMessage.groupChange = { + updates: [ + { + genericGroupUpdate: { + updaterAci: message.sourceServiceId + ? this.serviceIdToBytes(message.sourceServiceId) + : undefined, + }, + }, + ], + }; + + return { kind: NonBubbleResultKind.Directionless, patch }; } if (isUnsupportedMessage(message)) { diff --git a/ts/test-electron/backup/bubble_test.ts b/ts/test-electron/backup/bubble_test.ts index 79edbacc7e..946d80a4fe 100644 --- a/ts/test-electron/backup/bubble_test.ts +++ b/ts/test-electron/backup/bubble_test.ts @@ -8,13 +8,22 @@ import type { ConversationModel } from '../../models/conversations'; import { GiftBadgeStates } from '../../components/conversation/Message'; import Data from '../../sql/Client'; +import { getRandomBytes } from '../../Crypto'; +import * as Bytes from '../../Bytes'; import { generateAci } from '../../types/ServiceId'; import { ReadStatus } from '../../messages/MessageReadStatus'; import { SeenStatus } from '../../MessageSeenStatus'; import { loadCallsHistory } from '../../services/callHistoryLoader'; -import { setupBasics, symmetricRoundtripHarness, OUR_ACI } from './helpers'; +import { ID_V1_LENGTH } from '../../groups'; +import { + setupBasics, + asymmetricRoundtripHarness, + symmetricRoundtripHarness, + OUR_ACI, +} from './helpers'; const CONTACT_A = generateAci(); +const GV1_ID = Bytes.toBinary(getRandomBytes(ID_V1_LENGTH)); const BADGE_RECEIPT = 'AEpyZxbRBT+T5PQw9Wcx1QE2aFvL7LoLir9V4UF09Kk9qiP4SpIlHdlWHrAICy6F' + @@ -27,6 +36,7 @@ const BADGE_RECEIPT = describe('backup/bubble messages', () => { let contactA: ConversationModel; + let gv1: ConversationModel; beforeEach(async () => { await Data._removeAllMessages(); @@ -41,6 +51,14 @@ describe('backup/bubble messages', () => { { systemGivenName: 'CONTACT_A' } ); + gv1 = await window.ConversationController.getOrCreateAndWait( + GV1_ID, + 'group', + { + groupVersion: 1, + } + ); + await loadCallsHistory(); }); @@ -383,4 +401,26 @@ describe('backup/bubble messages', () => { }, ]); }); + + it('drops gv1 messages', async () => { + await asymmetricRoundtripHarness( + [ + { + conversationId: gv1.id, + id: generateGuid(), + type: 'incoming', + received_at: 3, + received_at_ms: 3, + sent_at: 3, + timestamp: 3, + sourceServiceId: CONTACT_A, + body: 'd', + readStatus: ReadStatus.Unread, + seenStatus: SeenStatus.Unseen, + unidentifiedDeliveryReceived: true, + }, + ], + [] + ); + }); }); diff --git a/ts/test-electron/backup/non_bubble_test.ts b/ts/test-electron/backup/non_bubble_test.ts index 889b0890bd..063c9ef4c5 100644 --- a/ts/test-electron/backup/non_bubble_test.ts +++ b/ts/test-electron/backup/non_bubble_test.ts @@ -17,7 +17,12 @@ import { DurationInSeconds } from '../../util/durations'; import { ReadStatus } from '../../messages/MessageReadStatus'; import { SeenStatus } from '../../MessageSeenStatus'; import { loadCallsHistory } from '../../services/callHistoryLoader'; -import { setupBasics, symmetricRoundtripHarness, OUR_ACI } from './helpers'; +import { + setupBasics, + asymmetricRoundtripHarness, + symmetricRoundtripHarness, + OUR_ACI, +} from './helpers'; const CONTACT_A = generateAci(); const GROUP_ID = Bytes.toBase64(getRandomBytes(32)); @@ -501,4 +506,40 @@ describe('backup/non-bubble messages', () => { }, ]); }); + + it('creates a tombstone for gv1 update in gv2 group', async () => { + await asymmetricRoundtripHarness( + [ + { + conversationId: group.id, + id: generateGuid(), + type: 'incoming', + received_at: 1, + received_at_ms: 1, + sourceServiceId: CONTACT_A, + sourceDevice: 1, + sent_at: 1, + timestamp: 1, + readStatus: ReadStatus.Unread, + seenStatus: SeenStatus.Unseen, + group_update: {}, + }, + ], + [ + { + conversationId: group.id, + id: 'does not matter', + type: 'group-v2-change', + groupV2Change: { + details: [{ type: 'summary' }], + from: CONTACT_A, + }, + received_at: 1, + sent_at: 1, + sourceServiceId: CONTACT_A, + timestamp: 1, + }, + ] + ); + }); });