diff --git a/protos/Backups.proto b/protos/Backups.proto index f9e70a08f0f..f2ca9e719e0 100644 --- a/protos/Backups.proto +++ b/protos/Backups.proto @@ -751,6 +751,7 @@ message SimpleChatUpdate { PAYMENTS_ACTIVATED = 10; PAYMENT_ACTIVATION_REQUEST = 11; UNSUPPORTED_PROTOCOL_MESSAGE = 12; + REPORTED_SPAM = 13; } Type type = 1; @@ -1042,7 +1043,7 @@ message ChatStyle { } message CustomChatColor { - uint32 id = 1; + uint64 id = 1; oneof color { fixed32 solid = 2; @@ -1116,7 +1117,7 @@ message ChatStyle { BubbleColorPreset bubbleColorPreset = 4; // See AccountSettings.customChatColors - uint32 customColorId = 5; + uint64 customColorId = 5; } bool dimWallpaperInDarkMode = 7; diff --git a/ts/services/backups/export.ts b/ts/services/backups/export.ts index 14d6493ec2e..aa8e4ae5364 100644 --- a/ts/services/backups/export.ts +++ b/ts/services/backups/export.ts @@ -27,6 +27,7 @@ import { import type { RawBodyRange } from '../../types/BodyRange'; import { LONG_ATTACHMENT_LIMIT } from '../../types/Message'; import { PaymentEventKind } from '../../types/Payment'; +import { MessageRequestResponseEvent } from '../../types/MessageRequestResponseEvent'; import type { ConversationAttributesType, MessageAttributesType, @@ -78,6 +79,7 @@ import { isChangeNumberNotification, isJoinedSignalNotification, isTitleTransitionNotification, + isMessageRequestResponse, } from '../../state/selectors/message'; import * as Bytes from '../../Bytes'; import { canBeSynced as canPreferredReactionEmojiBeSynced } from '../../reactions/preferredReactionEmoji'; @@ -181,7 +183,7 @@ export class BackupExportStream extends Readable { // Map from custom color uuid to an index in accountSettings.customColors // array. - private customColorIdByUuid = new Map(); + private customColorIdByUuid = new Map(); public run(backupLevel: BackupLevel): void { drop( @@ -1328,6 +1330,31 @@ export class BackupExportStream extends Readable { return { kind: NonBubbleResultKind.Directionless, patch }; } + if (isMessageRequestResponse(message)) { + const { messageRequestResponseEvent: event } = message; + if (event == null) { + return { kind: NonBubbleResultKind.Drop }; + } + + let type: Backups.SimpleChatUpdate.Type; + const { Type } = Backups.SimpleChatUpdate; + switch (event) { + case MessageRequestResponseEvent.ACCEPT: + case MessageRequestResponseEvent.BLOCK: + case MessageRequestResponseEvent.UNBLOCK: + return { kind: NonBubbleResultKind.Drop }; + case MessageRequestResponseEvent.SPAM: + type = Type.REPORTED_SPAM; + break; + default: + throw missingCaseError(event); + } + + updateMessage.simpleUpdate = { type }; + + return { kind: NonBubbleResultKind.Directionless, patch }; + } + if (isDeliveryIssue(message)) { updateMessage.simpleUpdate = { type: Backups.SimpleChatUpdate.Type.BAD_DECRYPT, @@ -2278,7 +2305,7 @@ export class BackupExportStream extends Readable { const result = new Array(); for (const [uuid, color] of Object.entries(customColors.colors)) { - const id = result.length; + const id = Long.fromNumber(result.length); this.customColorIdByUuid.set(uuid, id); const start = hslToRGBInt(color.start.hue, color.start.saturation); diff --git a/ts/services/backups/import.ts b/ts/services/backups/import.ts index f0b6522b78e..eb73833222c 100644 --- a/ts/services/backups/import.ts +++ b/ts/services/backups/import.ts @@ -23,6 +23,7 @@ import { import { isStoryDistributionId } from '../../types/StoryDistributionId'; import * as Errors from '../../types/errors'; import { PaymentEventKind } from '../../types/Payment'; +import { MessageRequestResponseEvent } from '../../types/MessageRequestResponseEvent'; import { ContactFormType, AddressType as ContactAddressType, @@ -2450,6 +2451,7 @@ export class BackupImportStream extends Writable { } ): Promise | undefined> { const { Type } = Backups.SimpleChatUpdate; + strictAssert(simpleUpdate.type != null, 'Simple update missing type'); switch (simpleUpdate.type) { case Type.END_SESSION: return { @@ -2493,7 +2495,6 @@ export class BackupImportStream extends Writable { case Type.RELEASE_CHANNEL_DONATION_REQUEST: log.warn('backups: dropping boost request from release notes'); return undefined; - // TODO(indutny): REPORTED_SPAM case Type.PAYMENTS_ACTIVATED: return { payment: { @@ -2513,8 +2514,13 @@ export class BackupImportStream extends Writable { requiredProtocolVersion: SignalService.DataMessage.ProtocolVersion.CURRENT - 1, }; + case Type.REPORTED_SPAM: + return { + type: 'message-request-response-event', + messageRequestResponseEvent: MessageRequestResponseEvent.SPAM, + }; default: - throw new Error('Not implemented'); + throw new Error(`Unsupported update type: ${simpleUpdate.type}`); } } @@ -2583,7 +2589,7 @@ export class BackupImportStream extends Writable { } customColors.colors[uuid] = value; - this.customColorById.set(color.id || 0, { + this.customColorById.set(color.id?.toNumber() || 0, { id: uuid, value, }); @@ -2703,7 +2709,9 @@ export class BackupImportStream extends Writable { } else { strictAssert(chatStyle.customColorId != null, 'Missing custom color id'); - const entry = this.customColorById.get(chatStyle.customColorId); + const entry = this.customColorById.get( + chatStyle.customColorId.toNumber() + ); strictAssert(entry != null, 'Missing custom color'); color = 'custom'; diff --git a/ts/state/selectors/message.ts b/ts/state/selectors/message.ts index d2b69cef8f3..a26473437fd 100644 --- a/ts/state/selectors/message.ts +++ b/ts/state/selectors/message.ts @@ -1022,7 +1022,8 @@ export function isNormalBubble(message: MessageWithUIFieldsType): boolean { !isVerifiedChange(message) && !isChangeNumberNotification(message) && !isJoinedSignalNotification(message) && - !isDeliveryIssue(message) + !isDeliveryIssue(message) && + !isMessageRequestResponse(message) ); } diff --git a/ts/test-electron/backup/non_bubble_test.ts b/ts/test-electron/backup/non_bubble_test.ts index 45ccbeda4f7..5242be2f4a7 100644 --- a/ts/test-electron/backup/non_bubble_test.ts +++ b/ts/test-electron/backup/non_bubble_test.ts @@ -13,6 +13,7 @@ import { DataWriter } from '../../sql/Client'; import { generateAci } from '../../types/ServiceId'; import { PaymentEventKind } from '../../types/Payment'; import { ContactFormType } from '../../types/EmbeddedContact'; +import { MessageRequestResponseEvent } from '../../types/MessageRequestResponseEvent'; import { DurationInSeconds } from '../../util/durations'; import { ReadStatus } from '../../messages/MessageReadStatus'; import { SeenStatus } from '../../MessageSeenStatus'; @@ -542,4 +543,20 @@ describe('backup/non-bubble messages', () => { ] ); }); + + it('roundtrips spam report message', async () => { + await symmetricRoundtripHarness([ + { + conversationId: contactA.id, + id: generateGuid(), + type: 'message-request-response-event', + received_at: 1, + sourceServiceId: CONTACT_A, + sourceDevice: 1, + sent_at: 1, + timestamp: 1, + messageRequestResponseEvent: MessageRequestResponseEvent.SPAM, + }, + ]); + }); });