From 6cb86277be503fa308af3252b7c006d27bb9e35f Mon Sep 17 00:00:00 2001 From: Fedor Indutny <79877362+indutny-signal@users.noreply.github.com> Date: Thu, 23 Jan 2025 07:39:46 -0800 Subject: [PATCH] Fix author id for e164-only 1:1 messages --- ts/services/backups/export.ts | 57 +++++++++++++++++--------- ts/test-electron/backup/bubble_test.ts | 41 +++++++++++++++++- 2 files changed, 77 insertions(+), 21 deletions(-) diff --git a/ts/services/backups/export.ts b/ts/services/backups/export.ts index a18e061410..da627d01b1 100644 --- a/ts/services/backups/export.ts +++ b/ts/services/backups/export.ts @@ -213,6 +213,17 @@ export class BackupExportStream extends Readable { readonly #serviceIdToRecipientId = new Map(); readonly #e164ToRecipientId = new Map(); readonly #roomIdToRecipientId = new Map(); + readonly #stats = { + adHocCalls: 0, + callLinks: 0, + conversations: 0, + chats: 0, + distributionLists: 0, + messages: 0, + skippedMessages: 0, + stickerPacks: 0, + fixedDirectMessages: 0, + }; #ourConversation?: ConversationAttributesType; #attachmentBackupJobs: Array = []; #buffers = new Array(); @@ -277,17 +288,6 @@ export class BackupExportStream extends Readable { }); await this.#flush(); - const stats = { - adHocCalls: 0, - callLinks: 0, - conversations: 0, - chats: 0, - distributionLists: 0, - messages: 0, - skippedMessages: 0, - stickerPacks: 0, - }; - const identityKeys = await DataReader.getAllIdentityKeys(); const identityKeysById = new Map( identityKeys.map(key => { @@ -318,7 +318,7 @@ export class BackupExportStream extends Readable { // eslint-disable-next-line no-await-in-loop await this.#flush(); - stats.conversations += 1; + this.#stats.conversations += 1; } this.#pushFrame({ @@ -375,7 +375,7 @@ export class BackupExportStream extends Readable { // eslint-disable-next-line no-await-in-loop await this.#flush(); - stats.distributionLists += 1; + this.#stats.distributionLists += 1; } const callLinks = await DataReader.getAllCallLinks(); @@ -417,7 +417,7 @@ export class BackupExportStream extends Readable { // eslint-disable-next-line no-await-in-loop await this.#flush(); - stats.callLinks += 1; + this.#stats.callLinks += 1; } const stickerPacks = await getStickerPacksForBackup(); @@ -432,7 +432,7 @@ export class BackupExportStream extends Readable { // eslint-disable-next-line no-await-in-loop await this.#flush(); - stats.stickerPacks += 1; + this.#stats.stickerPacks += 1; } const pinnedConversationIds = @@ -507,7 +507,7 @@ export class BackupExportStream extends Readable { // eslint-disable-next-line no-await-in-loop await this.#flush(); - stats.chats += 1; + this.#stats.chats += 1; } const allCallHistoryItems = await DataReader.getAllCallHistory(); @@ -538,7 +538,7 @@ export class BackupExportStream extends Readable { // eslint-disable-next-line no-await-in-loop await this.#flush(); - stats.adHocCalls += 1; + this.#stats.adHocCalls += 1; } let cursor: PageMessagesCursorType | undefined; @@ -575,7 +575,7 @@ export class BackupExportStream extends Readable { for (const chatItem of items) { if (chatItem === undefined) { - stats.skippedMessages += 1; + this.#stats.skippedMessages += 1; // Can't be backed up. continue; } @@ -586,7 +586,7 @@ export class BackupExportStream extends Readable { // eslint-disable-next-line no-await-in-loop await this.#flush(); - stats.messages += 1; + this.#stats.messages += 1; } cursor = newCursor; @@ -600,7 +600,7 @@ export class BackupExportStream extends Readable { await this.#flush(); log.warn('backups: final stats', { - ...stats, + ...this.#stats, attachmentBackupJobs: this.#attachmentBackupJobs.length, }); @@ -1030,6 +1030,23 @@ export class BackupExportStream extends Readable { serviceId: message.sourceServiceId, e164: message.source, }); + + if ( + isIncoming && + conversation && + isDirectConversation(conversation.attributes) + ) { + const convoAuthor = this.#getOrPushPrivateRecipient({ + id: conversation.attributes.id, + }); + + // Fix conversation id for misattributed e164-only incoming 1:1 + // messages. + if (authorId.neq(convoAuthor)) { + authorId = convoAuthor; + this.#stats.fixedDirectMessages += 1; + } + } } else { strictAssert(!isIncoming, 'Incoming message must have source'); diff --git a/ts/test-electron/backup/bubble_test.ts b/ts/test-electron/backup/bubble_test.ts index 21aabb6000..47656283ec 100644 --- a/ts/test-electron/backup/bubble_test.ts +++ b/ts/test-electron/backup/bubble_test.ts @@ -29,6 +29,7 @@ import { MY_STORY_ID } from '../../types/Stories'; const CONTACT_A = generateAci(); const CONTACT_B = generateAci(); +const CONTACT_B_E164 = '+12135550123'; const GV1_ID = Bytes.toBinary(getRandomBytes(ID_V1_LENGTH)); const BADGE_RECEIPT = @@ -60,7 +61,7 @@ describe('backup/bubble messages', () => { contactB = await window.ConversationController.getOrCreateAndWait( CONTACT_B, 'private', - { systemGivenName: 'CONTACT_B', active_at: 1 } + { systemGivenName: 'CONTACT_B', e164: CONTACT_B_E164, active_at: 1 } ); gv1 = await window.ConversationController.getOrCreateAndWait( @@ -233,6 +234,44 @@ describe('backup/bubble messages', () => { ]); }); + it('fixes e164-only incoming 1:1 messages', async () => { + await asymmetricRoundtripHarness( + [ + { + conversationId: contactA.id, + id: generateGuid(), + type: 'incoming', + received_at: 3, + received_at_ms: 3, + sent_at: 3, + // Note: contact B e164 vs contact A conversationId + source: CONTACT_B_E164, + readStatus: ReadStatus.Unread, + seenStatus: SeenStatus.Unseen, + unidentifiedDeliveryReceived: true, + timestamp: 3, + body: 'hello', + }, + ], + [ + { + conversationId: contactA.id, + id: generateGuid(), + type: 'incoming', + received_at: 3, + received_at_ms: 3, + sent_at: 3, + sourceServiceId: CONTACT_A, + readStatus: ReadStatus.Unread, + seenStatus: SeenStatus.Unseen, + unidentifiedDeliveryReceived: true, + timestamp: 3, + body: 'hello', + }, + ] + ); + }); + describe('quotes', () => { it('roundtrips gift badge quote', async () => { await symmetricRoundtripHarness([