From f90c2b74794ad7ef2f574aa27244d8b31076f02e Mon Sep 17 00:00:00 2001 From: Fedor Indutny <79877362+indutny-signal@users.noreply.github.com> Date: Thu, 29 Jun 2023 21:17:27 +0200 Subject: [PATCH] destinationServiceId in Sent --- protos/SignalService.proto | 15 +++- ts/background.ts | 30 ++++--- ts/jobs/conversationJobQueue.ts | 7 +- ts/jobs/helpers/sendDeleteForEveryone.ts | 5 +- ts/jobs/helpers/sendDeleteStoryForEveryone.ts | 14 +++- .../sendDirectExpirationTimerUpdate.ts | 3 +- ts/jobs/helpers/sendStory.ts | 14 +++- ts/models/messages.ts | 9 ++- ts/test-both/processSyncMessage_test.ts | 54 +++++++++++-- ts/textsecure/MessageReceiver.ts | 67 +++++++++------- ts/textsecure/SendMessage.ts | 19 +++-- ts/textsecure/Types.d.ts | 21 ++++- ts/textsecure/messageReceiverEvents.ts | 4 +- ts/textsecure/processSyncMessage.ts | 79 +++++++++++++------ ts/types/Stories.ts | 3 +- ts/types/UUID.ts | 11 +++ ts/util/deleteStoryForEveryone.ts | 12 ++- ts/util/getConversationUuid.ts | 49 ++++++++++++ ts/util/onStoryRecipientUpdate.ts | 7 +- ts/util/wrapWithSyncMessageSend.ts | 3 +- 20 files changed, 322 insertions(+), 104 deletions(-) create mode 100644 ts/util/getConversationUuid.ts diff --git a/protos/SignalService.proto b/protos/SignalService.proto index 8c2119eae44..95021b71268 100644 --- a/protos/SignalService.proto +++ b/protos/SignalService.proto @@ -457,18 +457,27 @@ message SyncMessage { message Sent { message UnidentifiedDeliveryStatus { optional string destination = 1; - optional string destinationUuid = 3; + oneof destinationServiceId { + string destinationAci = 3; + string destinationPni = 4; + } optional bool unidentified = 2; } message StoryMessageRecipient { - optional string destinationUuid = 1; + oneof destinationServiceId { + string destinationAci = 1; + string destinationPni = 4; + } repeated string distributionListIds = 2; optional bool isAllowedToReply = 3; } optional string destination = 1; - optional string destinationUuid = 7; + oneof destinationServiceId { + string destinationAci = 7; + string destinationPni = 11; + } optional uint64 timestamp = 2; optional DataMessage message = 3; optional uint64 expirationStartTimestamp = 4; diff --git a/ts/background.ts b/ts/background.ts index 178e7e8969d..1fe684fc0b9 100644 --- a/ts/background.ts +++ b/ts/background.ts @@ -149,6 +149,7 @@ import { themeChanged } from './shims/themeChanged'; import { createIPCEvents } from './util/createIPCEvents'; import { RemoveAllConfiguration } from './types/RemoveAllConfiguration'; import { isValidUuid, UUIDKind, UUID } from './types/UUID'; +import type { TaggedUUIDStringType } from './types/UUID'; import * as log from './logging/log'; import { loadRecentEmojis } from './util/loadRecentEmojis'; import { deleteAllLogs } from './util/deleteAllLogs'; @@ -2463,7 +2464,9 @@ export async function startApp(): Promise { message: data.message, // 'message' event: for 1:1 converations, the conversation is same as sender destination: data.source, - destinationUuid: data.sourceUuid, + destinationUuid: { + aci: data.sourceUuid, + }, }); const { PROFILE_KEY_UPDATE } = Proto.DataMessage.Flags; @@ -2699,11 +2702,9 @@ export async function startApp(): Promise { result: SendStateByConversationId, { destinationUuid, destination, isAllowedToReplyToStory } ) => { - const conversation = window.ConversationController.lookupOrCreate({ - uuid: destinationUuid, - e164: destination, - reason: 'createSentMessage', - }); + const conversation = window.ConversationController.get( + destinationUuid?.aci || destinationUuid?.pni || destination + ); if (!conversation || conversation.id === ourId) { return result; } @@ -2729,7 +2730,12 @@ export async function startApp(): Promise { if (unidentifiedStatus.length) { unidentifiedDeliveries = unidentifiedStatus .filter(item => Boolean(item.unidentified)) - .map(item => item.destinationUuid || item.destination) + .map( + item => + item.destinationUuid?.aci || + item.destinationUuid?.pni || + item.destination + ) .filter(isNotNil); } @@ -2770,7 +2776,7 @@ export async function startApp(): Promise { }: { message: ProcessedDataMessage; destination?: string; - destinationUuid?: string; + destinationUuid?: TaggedUUIDStringType; }): MessageDescriptor => { if (message.groupV2) { const { id } = message.groupV2; @@ -2810,11 +2816,9 @@ export async function startApp(): Promise { }; } - const conversation = window.ConversationController.lookupOrCreate({ - uuid: destinationUuid, - e164: destination, - reason: `getMessageDescriptor(${message.timestamp}): private`, - }); + const conversation = window.ConversationController.get( + destinationUuid?.aci || destinationUuid?.pni || destination + ); strictAssert(conversation, 'Destination conversation cannot be created'); return { diff --git a/ts/jobs/conversationJobQueue.ts b/ts/jobs/conversationJobQueue.ts index 5a3ee8f393d..46351e8ce70 100644 --- a/ts/jobs/conversationJobQueue.ts +++ b/ts/jobs/conversationJobQueue.ts @@ -82,7 +82,12 @@ const deleteStoryForEveryoneJobDataSchema = z.object({ updatedStoryRecipients: z .array( z.object({ - destinationUuid: z.string(), + // TODO: DESKTOP-5630 + destinationUuid: z.string().optional(), + legacyDestinationUuid: z.string().optional(), + + destinationAci: z.string().optional(), + destinationPni: z.string().optional(), distributionListIds: z.array(z.string()), isAllowedToReply: z.boolean(), }) diff --git a/ts/jobs/helpers/sendDeleteForEveryone.ts b/ts/jobs/helpers/sendDeleteForEveryone.ts index 3e7e4b6e62a..447fe044d6d 100644 --- a/ts/jobs/helpers/sendDeleteForEveryone.ts +++ b/ts/jobs/helpers/sendDeleteForEveryone.ts @@ -36,6 +36,7 @@ import { strictAssert } from '../../util/assert'; import type { LoggerType } from '../../types/Logging'; import { isStory } from '../../messages/helpers'; import { sendToGroup } from '../../util/sendToGroup'; +import { getTaggedConversationUuid } from '../../util/getConversationUuid'; export async function sendDeleteForEveryone( conversation: ConversationModel, @@ -139,7 +140,9 @@ export async function sendDeleteForEveryone( proto.dataMessage ).finish(), destination: conversation.get('e164'), - destinationUuid: conversation.get('uuid'), + destinationUuid: getTaggedConversationUuid( + conversation.attributes + ), expirationStartTimestamp: null, options: sendOptions, timestamp, diff --git a/ts/jobs/helpers/sendDeleteStoryForEveryone.ts b/ts/jobs/helpers/sendDeleteStoryForEveryone.ts index f4938eddb1a..166e59fbee6 100644 --- a/ts/jobs/helpers/sendDeleteStoryForEveryone.ts +++ b/ts/jobs/helpers/sendDeleteStoryForEveryone.ts @@ -239,8 +239,18 @@ export async function sendDeleteStoryForEveryone( await handleMessageSend( messaging.sendSyncMessage({ destination: undefined, - destinationUuid, - storyMessageRecipients: updatedStoryRecipients, + destinationUuid: { + aci: destinationUuid, + }, + storyMessageRecipients: updatedStoryRecipients?.map( + ({ destinationUuid: legacyDestinationUuid, ...rest }) => { + return { + // The field was renamed. + legacyDestinationUuid, + ...rest, + }; + } + ), expirationStartTimestamp: null, isUpdate: true, options, diff --git a/ts/jobs/helpers/sendDirectExpirationTimerUpdate.ts b/ts/jobs/helpers/sendDirectExpirationTimerUpdate.ts index 6163114f191..bce04da5fb4 100644 --- a/ts/jobs/helpers/sendDirectExpirationTimerUpdate.ts +++ b/ts/jobs/helpers/sendDirectExpirationTimerUpdate.ts @@ -20,6 +20,7 @@ import { handleMessageSend } from '../../util/handleMessageSend'; import { isConversationAccepted } from '../../util/isConversationAccepted'; import { isConversationUnregistered } from '../../util/isConversationUnregistered'; import { DurationInSeconds } from '../../util/durations'; +import { getTaggedConversationUuid } from '../../util/getConversationUuid'; export async function sendDirectExpirationTimerUpdate( conversation: ConversationModel, @@ -107,7 +108,7 @@ export async function sendDirectExpirationTimerUpdate( proto.dataMessage ).finish(), destination: conversation.get('e164'), - destinationUuid: conversation.get('uuid'), + destinationUuid: getTaggedConversationUuid(conversation.attributes), expirationStartTimestamp: null, options: sendOptions, timestamp, diff --git a/ts/jobs/helpers/sendStory.ts b/ts/jobs/helpers/sendStory.ts index 2283008cede..bca8c998d2f 100644 --- a/ts/jobs/helpers/sendStory.ts +++ b/ts/jobs/helpers/sendStory.ts @@ -34,6 +34,7 @@ import { handleMultipleSendErrors } from './handleMultipleSendErrors'; import { isGroupV2, isMe } from '../../util/whatTypeOfConversation'; import { ourProfileKeyService } from '../../services/ourProfileKey'; import { sendContentMessageToGroup } from '../../util/sendToGroup'; +import { getTaggedConversationUuid } from '../../util/getConversationUuid'; import { distributionListToSendTarget } from '../../util/distributionListToSendTarget'; import { uploadAttachment } from '../../util/uploadAttachment'; import { SendMessageChallengeError } from '../../textsecure/Errors'; @@ -548,8 +549,17 @@ export async function sendStory( // Build up the sync message's storyMessageRecipients and send it const storyMessageRecipients: StoryMessageRecipientsType = []; recipientsByUuid.forEach((distributionListIds, destinationUuid) => { + const recipient = window.ConversationController.get(destinationUuid); + if (!recipient) { + return; + } + const taggedUuid = getTaggedConversationUuid(recipient.attributes); + if (!taggedUuid) { + return; + } storyMessageRecipients.push({ - destinationUuid, + destinationAci: taggedUuid.aci, + destinationPni: taggedUuid.pni, distributionListIds: Array.from(distributionListIds), isAllowedToReply: canReplyUuids.has(destinationUuid), }); @@ -567,7 +577,7 @@ export async function sendStory( await messaging.sendSyncMessage({ // Note: these two fields will be undefined if we're sending to a group destination: conversation.get('e164'), - destinationUuid: conversation.get('uuid'), + destinationUuid: getTaggedConversationUuid(conversation.attributes), storyMessage: originalStoryMessage, storyMessageRecipients, expirationStartTimestamp: null, diff --git a/ts/models/messages.ts b/ts/models/messages.ts index a7b9a0d277a..2aa22ad0d13 100644 --- a/ts/models/messages.ts +++ b/ts/models/messages.ts @@ -49,6 +49,7 @@ import { SendMessageProtoError } from '../textsecure/Errors'; import * as expirationTimer from '../util/expirationTimer'; import { getUserLanguages } from '../util/userLanguages'; import { getMessageSentTimestamp } from '../util/getMessageSentTimestamp'; +import { getTaggedConversationUuid } from '../util/getConversationUuid'; import type { ReactionType } from '../types/Reactions'; import { UUID, UUIDKind } from '../types/UUID'; @@ -1823,7 +1824,7 @@ export class MessageModel extends window.Backbone.Model { ...encodedContent, timestamp, destination: conv.get('e164'), - destinationUuid: conv.get('uuid'), + destinationUuid: getTaggedConversationUuid(conv.attributes), expirationStartTimestamp: this.get('expirationStartTimestamp') || null, conversationIdsSentTo, @@ -2221,14 +2222,16 @@ export class MessageModel extends window.Backbone.Model { unidentifiedStatus.forEach( ({ destinationUuid, destination, unidentified }) => { - const identifier = destinationUuid || destination; + const identifier = + destinationUuid?.aci || destinationUuid?.pni || destination; if (!identifier) { return; } const { conversation: destinationConversation } = window.ConversationController.maybeMergeContacts({ - aci: destinationUuid, + aci: destinationUuid?.aci, + pni: destinationUuid?.pni, e164: destination || undefined, reason: `handleDataMessage(${initialMessage.timestamp})`, }); diff --git a/ts/test-both/processSyncMessage_test.ts b/ts/test-both/processSyncMessage_test.ts index e0e8f9af8fe..76fc3fe0d32 100644 --- a/ts/test-both/processSyncMessage_test.ts +++ b/ts/test-both/processSyncMessage_test.ts @@ -2,21 +2,21 @@ // SPDX-License-Identifier: AGPL-3.0-only import { assert } from 'chai'; -import getGuid from 'uuid/v4'; +import { UUID } from '../types/UUID'; import { processSyncMessage } from '../textsecure/processSyncMessage'; describe('processSyncMessage', () => { - it('should normalize UUIDs in sent', () => { - const destinationUuid = getGuid(); + const destinationUuid = UUID.generate().toString(); + it('should normalize UUIDs in sent (aci)', () => { const out = processSyncMessage({ sent: { - destinationUuid: destinationUuid.toUpperCase(), + destinationAci: destinationUuid.toUpperCase(), unidentifiedStatus: [ { - destinationUuid: destinationUuid.toUpperCase(), + destinationAci: destinationUuid.toUpperCase(), }, ], }, @@ -24,11 +24,51 @@ describe('processSyncMessage', () => { assert.deepStrictEqual(out, { sent: { - destinationUuid, + destinationUuid: { + aci: destinationUuid, + pni: undefined, + }, + + storyMessageRecipients: undefined, + unidentifiedStatus: [ + { + destinationUuid: { + aci: destinationUuid, + pni: undefined, + }, + }, + ], + }, + }); + }); + + it('should normalize UUIDs in sent (pni)', () => { + const out = processSyncMessage({ + sent: { + destinationPni: destinationUuid.toUpperCase(), unidentifiedStatus: [ { - destinationUuid, + destinationPni: destinationUuid.toUpperCase(), + }, + ], + }, + }); + + assert.deepStrictEqual(out, { + sent: { + destinationUuid: { + aci: undefined, + pni: destinationUuid, + }, + + storyMessageRecipients: undefined, + unidentifiedStatus: [ + { + destinationUuid: { + aci: undefined, + pni: destinationUuid, + }, }, ], }, diff --git a/ts/textsecure/MessageReceiver.ts b/ts/textsecure/MessageReceiver.ts index 9841ee27f95..7e74a9152c4 100644 --- a/ts/textsecure/MessageReceiver.ts +++ b/ts/textsecure/MessageReceiver.ts @@ -52,7 +52,7 @@ import { bytesToUuid } from '../Crypto'; import type { DownloadedAttachmentType } from '../types/Attachment'; import { Address } from '../types/Address'; import { QualifiedAddress } from '../types/QualifiedAddress'; -import type { UUIDStringType } from '../types/UUID'; +import type { UUIDStringType, TaggedUUIDStringType } from '../types/UUID'; import { UUID, UUIDKind } from '../types/UUID'; import * as Errors from '../types/errors'; @@ -2028,8 +2028,9 @@ export default class MessageReceiver let p: Promise = Promise.resolve(); if (msg.flags && msg.flags & Proto.DataMessage.Flags.END_SESSION) { - if (destinationUuid) { - p = this.handleEndSession(envelope, new UUID(destinationUuid)); + const anyUuid = destinationUuid?.aci ?? destinationUuid?.pni; + if (anyUuid) { + p = this.handleEndSession(envelope, new UUID(anyUuid)); } else if (destination) { const theirUuid = UUID.lookup(destination); if (theirUuid) { @@ -2061,8 +2062,7 @@ export default class MessageReceiver const ev = new SentEvent( { destination: dropNull(destination), - destinationUuid: - dropNull(destinationUuid) || envelope.destinationUuid.toString(), + destinationUuid, timestamp: timestamp?.toNumber(), serverTimestamp: envelope.serverTimestamp, device: envelope.sourceDevice, @@ -2168,7 +2168,9 @@ export default class MessageReceiver if (sentMessage && message.groupV2) { const ev = new SentEvent( { - destinationUuid: envelope.destinationUuid.toString(), + destinationUuid: { + aci: envelope.destinationUuid.toString(), + }, device: envelope.sourceDevice, isRecipientUpdate: Boolean(sentMessage.isRecipientUpdate), message, @@ -2183,10 +2185,7 @@ export default class MessageReceiver } return { - destinationUuid: normalizeUuid( - destinationUuid, - 'handleStoryMessage.destinationUuid' - ), + destinationUuid, isAllowedToReplyToStory: Boolean(isAllowedToReply), }; }) @@ -2202,25 +2201,26 @@ export default class MessageReceiver const { storyMessageRecipients } = sentMessage; const recipients = storyMessageRecipients ?? []; - const isAllowedToReply = new Map(); - const distributionListToSentUuid = new Map>(); + const isAllowedToReply = new Map(); + const distributionListToSentUuid = new Map< + string, + Map + >(); recipients.forEach(recipient => { const { destinationUuid } = recipient; - if (!destinationUuid) { + if (!destinationUuid?.aci && !destinationUuid?.pni) { return; } - const normalizedDestinationUuid = normalizeUuid( - destinationUuid, - 'handleStoryMessage.destinationUuid' - ); + const destinationUuidString = + destinationUuid?.aci || destinationUuid?.pni; if (recipient.distributionListIds) { recipient.distributionListIds.forEach(listId => { - const sentUuids: Set = - distributionListToSentUuid.get(listId) || new Set(); - sentUuids.add(normalizedDestinationUuid); + const sentUuids: Map = + distributionListToSentUuid.get(listId) || new Map(); + sentUuids.set(destinationUuidString, destinationUuid); distributionListToSentUuid.set(listId, sentUuids); }); } else { @@ -2232,7 +2232,7 @@ export default class MessageReceiver } isAllowedToReply.set( - normalizedDestinationUuid, + destinationUuidString, recipient.isAllowedToReply !== false ); }); @@ -2240,14 +2240,22 @@ export default class MessageReceiver distributionListToSentUuid.forEach((sentToUuids, listId) => { const ev = new SentEvent( { - destinationUuid: envelope.destinationUuid.toString(), + destinationUuid: { + aci: envelope.destinationUuid.toString(), + pni: undefined, + }, timestamp: envelope.timestamp, serverTimestamp: envelope.serverTimestamp, device: envelope.sourceDevice, - unidentifiedStatus: Array.from(sentToUuids).map( + unidentifiedStatus: Array.from(sentToUuids.values()).map( destinationUuid => ({ destinationUuid, - isAllowedToReplyToStory: isAllowedToReply.get(destinationUuid), + isAllowedToReplyToStory: Boolean( + (destinationUuid.aci && + isAllowedToReply.get(destinationUuid.aci)) || + (destinationUuid.pni && + isAllowedToReply.get(destinationUuid.pni)) + ), }) ), message, @@ -2867,11 +2875,15 @@ export default class MessageReceiver return undefined; } - private getDestination(sentMessage: Proto.SyncMessage.ISent) { + private getDestination(sentMessage: ProcessedSent) { if (sentMessage.message && sentMessage.message.groupV2) { return `groupv2(${this.getGroupId(sentMessage.message)})`; } - return sentMessage.destination || sentMessage.destinationUuid; + return ( + sentMessage.destination || + sentMessage.destinationUuid?.aci || + sentMessage.destinationUuid?.pni + ); } private async handleSyncMessage( @@ -3067,8 +3079,7 @@ export default class MessageReceiver const ev = new SentEvent( { destination: dropNull(destination), - destinationUuid: - dropNull(destinationUuid) || envelope.destinationUuid.toString(), + destinationUuid, timestamp: envelope.timestamp, serverTimestamp: envelope.serverTimestamp, device: envelope.sourceDevice, diff --git a/ts/textsecure/SendMessage.ts b/ts/textsecure/SendMessage.ts index e0aa4b6c7b7..74af95402fb 100644 --- a/ts/textsecure/SendMessage.ts +++ b/ts/textsecure/SendMessage.ts @@ -17,6 +17,7 @@ import type { ConversationModel } from '../models/conversations'; import { GLOBAL_ZONE } from '../SignalProtocolStore'; import { assertDev, strictAssert } from '../util/assert'; import { parseIntOrThrow } from '../util/parseIntOrThrow'; +import { getTaggedConversationUuid } from '../util/getConversationUuid'; import { Address } from '../types/Address'; import { QualifiedAddress } from '../types/QualifiedAddress'; import { SenderKeys } from '../LibSignalStores'; @@ -24,7 +25,7 @@ import type { TextAttachmentType, UploadedAttachmentType, } from '../types/Attachment'; -import type { UUID } from '../types/UUID'; +import type { UUID, TaggedUUIDStringType } from '../types/UUID'; import type { ChallengeType, GetGroupLogOptionsType, @@ -1205,7 +1206,7 @@ export default class MessageSender { encodedEditMessage?: Uint8Array; timestamp: number; destination: string | undefined; - destinationUuid: string | null | undefined; + destinationUuid: TaggedUUIDStringType | undefined; expirationStartTimestamp: number | null; conversationIdsSentTo?: Iterable; conversationIdsWithSealedSender?: Set; @@ -1230,8 +1231,10 @@ export default class MessageSender { if (destination) { sentMessage.destination = destination; } - if (destinationUuid) { - sentMessage.destinationUuid = destinationUuid; + if (destinationUuid?.aci) { + sentMessage.destinationAci = destinationUuid.aci; + } else if (destinationUuid?.pni) { + sentMessage.destinationPni = destinationUuid.pni; } if (expirationStartTimestamp) { sentMessage.expirationStartTimestamp = Long.fromNumber( @@ -1262,9 +1265,11 @@ export default class MessageSender { if (e164) { status.destination = e164; } - const uuid = conv.get('uuid'); - if (uuid) { - status.destinationUuid = uuid; + const taggedUuid = getTaggedConversationUuid(conv.attributes); + if (taggedUuid?.aci) { + status.destinationAci = taggedUuid.aci; + } else if (taggedUuid?.pni) { + status.destinationPni = taggedUuid.pni; } } status.unidentified = diff --git a/ts/textsecure/Types.d.ts b/ts/textsecure/Types.d.ts index 27bf0abf18f..f416722208f 100644 --- a/ts/textsecure/Types.d.ts +++ b/ts/textsecure/Types.d.ts @@ -3,7 +3,7 @@ import type { SignalService as Proto } from '../protobuf'; import type { IncomingWebSocketRequest } from './WebsocketResources'; -import type { UUID, UUIDStringType } from '../types/UUID'; +import type { UUID, UUIDStringType, TaggedUUIDStringType } from '../types/UUID'; import type { TextAttachmentType } from '../types/Attachment'; import type { GiftBadgeStates } from '../components/conversation/Message'; import type { MIMEType } from '../types/MIME'; @@ -222,18 +222,31 @@ export type ProcessedDataMessage = { export type ProcessedUnidentifiedDeliveryStatus = Omit< Proto.SyncMessage.Sent.IUnidentifiedDeliveryStatus, - 'destinationUuid' + 'destinationAci' | 'destinationPni' > & { - destinationUuid?: string; + destinationUuid?: TaggedUUIDStringType; isAllowedToReplyToStory?: boolean; }; +export type ProcessedStoryMessageRecipient = Omit< + Proto.SyncMessage.Sent.IStoryMessageRecipient, + 'destinationAci' | 'destinationPni' +> & { + destinationUuid?: TaggedUUIDStringType; +}; + export type ProcessedSent = Omit< Proto.SyncMessage.ISent, - 'destinationId' | 'unidentifiedStatus' + | 'destinationId' + | 'unidentifiedStatus' + | 'storyMessageRecipients' + | 'destinationAci' + | 'destinationPni' > & { destinationId?: string; + destinationUuid?: TaggedUUIDStringType; unidentifiedStatus?: Array; + storyMessageRecipients?: Array; }; export type ProcessedSyncMessage = Omit & { diff --git a/ts/textsecure/messageReceiverEvents.ts b/ts/textsecure/messageReceiverEvents.ts index 5df3991e2c4..f62ca94c740 100644 --- a/ts/textsecure/messageReceiverEvents.ts +++ b/ts/textsecure/messageReceiverEvents.ts @@ -5,7 +5,7 @@ import type { PublicKey } from '@signalapp/libsignal-client'; import type { SignalService as Proto } from '../protobuf'; -import type { UUIDStringType } from '../types/UUID'; +import type { UUIDStringType, TaggedUUIDStringType } from '../types/UUID'; import type { ProcessedEnvelope, ProcessedDataMessage, @@ -193,7 +193,7 @@ export class RetryRequestEvent extends ConfirmableEvent { export type SentEventData = Readonly<{ destination?: string; - destinationUuid?: string; + destinationUuid?: TaggedUUIDStringType; timestamp?: number; serverTimestamp?: number; device: number | undefined; diff --git a/ts/textsecure/processSyncMessage.ts b/ts/textsecure/processSyncMessage.ts index 71ef42e5c4b..976cde4edb8 100644 --- a/ts/textsecure/processSyncMessage.ts +++ b/ts/textsecure/processSyncMessage.ts @@ -1,30 +1,50 @@ // Copyright 2021 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import { SignalService as Proto } from '../protobuf'; +import type { SignalService as Proto } from '../protobuf'; import { normalizeUuid } from '../util/normalizeUuid'; -import type { - ProcessedUnidentifiedDeliveryStatus, - ProcessedSent, - ProcessedSyncMessage, -} from './Types.d'; +import type { ProcessedSent, ProcessedSyncMessage } from './Types.d'; +import type { TaggedUUIDStringType } from '../types/UUID'; -import UnidentifiedDeliveryStatus = Proto.SyncMessage.Sent.IUnidentifiedDeliveryStatus; +type ProtoUUIDTriple = Readonly<{ + destinationAci?: string | null; + destinationPni?: string | null; +}>; -function processUnidentifiedDeliveryStatus( - status: UnidentifiedDeliveryStatus -): ProcessedUnidentifiedDeliveryStatus { - const { destinationUuid } = status; +function toTaggedUuid({ + destinationAci, + destinationPni, +}: ProtoUUIDTriple): TaggedUUIDStringType | undefined { + if (destinationAci) { + return { + aci: normalizeUuid(destinationAci, 'syncMessage.sent.destinationAci'), + pni: undefined, + }; + } + if (destinationPni) { + return { + aci: undefined, + pni: normalizeUuid(destinationPni, 'syncMessage.sent.destinationPni'), + }; + } + + return undefined; +} + +function processProtoWithDestinationUuid( + input: Input +): Omit & { + destinationUuid?: TaggedUUIDStringType; +} { + const { destinationAci, destinationPni, ...remaining } = input; return { - ...status, + ...remaining, - destinationUuid: destinationUuid - ? normalizeUuid( - destinationUuid, - 'syncMessage.sent.unidentifiedStatus.destinationUuid' - ) - : undefined, + destinationUuid: toTaggedUuid({ + destinationAci, + destinationPni, + }), }; } @@ -35,17 +55,26 @@ function processSent( return undefined; } - const { destinationUuid, unidentifiedStatus } = sent; + const { + destinationAci, + destinationPni, + unidentifiedStatus, + storyMessageRecipients, + ...remaining + } = sent; return { - ...sent, - - destinationUuid: destinationUuid - ? normalizeUuid(destinationUuid, 'syncMessage.sent.destinationUuid') - : undefined, + ...remaining, + destinationUuid: toTaggedUuid({ + destinationAci, + destinationPni, + }), unidentifiedStatus: unidentifiedStatus - ? unidentifiedStatus.map(processUnidentifiedDeliveryStatus) + ? unidentifiedStatus.map(processProtoWithDestinationUuid) + : undefined, + storyMessageRecipients: storyMessageRecipients + ? storyMessageRecipients.map(processProtoWithDestinationUuid) : undefined, }; } diff --git a/ts/types/Stories.ts b/ts/types/Stories.ts index 8af8e9cdd0c..cc599eeb36d 100644 --- a/ts/types/Stories.ts +++ b/ts/types/Stories.ts @@ -170,7 +170,8 @@ export enum ResolvedSendStatus { } export type StoryMessageRecipientsType = Array<{ - destinationUuid: string; + destinationAci?: string; + destinationPni?: string; distributionListIds: Array; isAllowedToReply: boolean; }>; diff --git a/ts/types/UUID.ts b/ts/types/UUID.ts index 0527f436cc4..bec4a694d22 100644 --- a/ts/types/UUID.ts +++ b/ts/types/UUID.ts @@ -8,6 +8,17 @@ import { strictAssert } from '../util/assert'; export type UUIDStringType = `${string}-${string}-${string}-${string}-${string}`; +export type TaggedUUIDStringType = Readonly< + | { + aci: UUIDStringType; + pni?: undefined; + } + | { + aci?: undefined; + pni: UUIDStringType; + } +>; + export enum UUIDKind { ACI = 'ACI', PNI = 'PNI', diff --git a/ts/util/deleteStoryForEveryone.ts b/ts/util/deleteStoryForEveryone.ts index 075ce56dc15..c323be3eab9 100644 --- a/ts/util/deleteStoryForEveryone.ts +++ b/ts/util/deleteStoryForEveryone.ts @@ -21,6 +21,7 @@ import { getMessageById } from '../messages/getMessageById'; import { strictAssert } from './assert'; import { repeat, zipObject } from './iterables'; import { isOlderThan } from './timestamp'; +import { getTaggedConversationUuid } from './getConversationUuid'; export async function deleteStoryForEveryone( stories: ReadonlyArray, @@ -157,8 +158,17 @@ export async function deleteStoryForEveryone( const newStoryMessageRecipients: StoryMessageRecipientsType = []; newStoryRecipients.forEach((recipientData, destinationUuid) => { + const recipient = window.ConversationController.get(destinationUuid); + if (!recipient) { + return; + } + const taggedUuid = getTaggedConversationUuid(recipient.attributes); + if (!taggedUuid) { + return; + } newStoryMessageRecipients.push({ - destinationUuid, + destinationAci: taggedUuid.aci, + destinationPni: taggedUuid.pni, distributionListIds: Array.from(recipientData.distributionListIds), isAllowedToReply: recipientData.isAllowedToReply, }); diff --git a/ts/util/getConversationUuid.ts b/ts/util/getConversationUuid.ts new file mode 100644 index 00000000000..a47a4e41be0 --- /dev/null +++ b/ts/util/getConversationUuid.ts @@ -0,0 +1,49 @@ +// Copyright 2023 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import { UUIDKind } from '../types/UUID'; +import type { UUIDStringType, TaggedUUIDStringType } from '../types/UUID'; +import type { ConversationAttributesType } from '../model-types.d'; +import { strictAssert } from './assert'; + +export type MinimalConversationType = Pick< + ConversationAttributesType, + 'uuid' | 'pni' +>; + +export function getConversationUuid( + { uuid, pni }: MinimalConversationType, + uuidKind = UUIDKind.ACI +): UUIDStringType | undefined { + if (uuidKind === UUIDKind.PNI) { + return pni; + } + + strictAssert( + uuidKind === UUIDKind.ACI, + 'getConversationUuid accepts either ACI or PNI uuid kind' + ); + + // When we know only PNI - we put PNI into both `uuid` and `pni` fields. + if (pni === uuid) { + return undefined; + } + + return uuid; +} + +export function getTaggedConversationUuid( + attributes: MinimalConversationType +): TaggedUUIDStringType | undefined { + const aci = getConversationUuid(attributes, UUIDKind.ACI); + if (aci) { + return { aci, pni: undefined }; + } + + const pni = getConversationUuid(attributes, UUIDKind.PNI); + if (pni) { + return { aci: undefined, pni }; + } + + return undefined; +} diff --git a/ts/util/onStoryRecipientUpdate.ts b/ts/util/onStoryRecipientUpdate.ts index 158742933e8..9bdae0284b7 100644 --- a/ts/util/onStoryRecipientUpdate.ts +++ b/ts/util/onStoryRecipientUpdate.ts @@ -57,12 +57,15 @@ export async function onStoryRecipientUpdate( Set >(); data.storyMessageRecipients.forEach(item => { - if (!item.destinationUuid) { + const { destinationAci, destinationPni } = item; + + const destinationId = destinationAci || destinationPni; + if (!destinationId) { return; } const convo = window.ConversationController.get( - normalizeUuid(item.destinationUuid, `${logId}.destinationUuid`) + normalizeUuid(destinationId, `${logId}.destinationId`) ); if (!convo || !item.distributionListIds) { diff --git a/ts/util/wrapWithSyncMessageSend.ts b/ts/util/wrapWithSyncMessageSend.ts index 3e0c984cae1..432bcfa8f8d 100644 --- a/ts/util/wrapWithSyncMessageSend.ts +++ b/ts/util/wrapWithSyncMessageSend.ts @@ -6,6 +6,7 @@ import * as log from '../logging/log'; import { SendMessageProtoError } from '../textsecure/Errors'; import { getSendOptions } from './getSendOptions'; import { handleMessageSend } from './handleMessageSend'; +import { getTaggedConversationUuid } from './getConversationUuid'; import type { CallbackResultType } from '../textsecure/Types.d'; import type { ConversationModel } from '../models/conversations'; @@ -77,7 +78,7 @@ export async function wrapWithSyncMessageSend({ await handleMessageSend( sender.sendSyncMessage({ destination: conversation.get('e164'), - destinationUuid: conversation.get('uuid'), + destinationUuid: getTaggedConversationUuid(conversation.attributes), encodedDataMessage: dataMessage, expirationStartTimestamp: null, options,