destinationServiceId in Sent

This commit is contained in:
Fedor Indutny 2023-06-29 21:17:27 +02:00 committed by GitHub
parent af4ad55c68
commit f90c2b7479
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 322 additions and 104 deletions

View file

@ -457,18 +457,27 @@ message SyncMessage {
message Sent { message Sent {
message UnidentifiedDeliveryStatus { message UnidentifiedDeliveryStatus {
optional string destination = 1; optional string destination = 1;
optional string destinationUuid = 3; oneof destinationServiceId {
string destinationAci = 3;
string destinationPni = 4;
}
optional bool unidentified = 2; optional bool unidentified = 2;
} }
message StoryMessageRecipient { message StoryMessageRecipient {
optional string destinationUuid = 1; oneof destinationServiceId {
string destinationAci = 1;
string destinationPni = 4;
}
repeated string distributionListIds = 2; repeated string distributionListIds = 2;
optional bool isAllowedToReply = 3; optional bool isAllowedToReply = 3;
} }
optional string destination = 1; optional string destination = 1;
optional string destinationUuid = 7; oneof destinationServiceId {
string destinationAci = 7;
string destinationPni = 11;
}
optional uint64 timestamp = 2; optional uint64 timestamp = 2;
optional DataMessage message = 3; optional DataMessage message = 3;
optional uint64 expirationStartTimestamp = 4; optional uint64 expirationStartTimestamp = 4;

View file

@ -149,6 +149,7 @@ import { themeChanged } from './shims/themeChanged';
import { createIPCEvents } from './util/createIPCEvents'; import { createIPCEvents } from './util/createIPCEvents';
import { RemoveAllConfiguration } from './types/RemoveAllConfiguration'; import { RemoveAllConfiguration } from './types/RemoveAllConfiguration';
import { isValidUuid, UUIDKind, UUID } from './types/UUID'; import { isValidUuid, UUIDKind, UUID } from './types/UUID';
import type { TaggedUUIDStringType } from './types/UUID';
import * as log from './logging/log'; import * as log from './logging/log';
import { loadRecentEmojis } from './util/loadRecentEmojis'; import { loadRecentEmojis } from './util/loadRecentEmojis';
import { deleteAllLogs } from './util/deleteAllLogs'; import { deleteAllLogs } from './util/deleteAllLogs';
@ -2463,7 +2464,9 @@ export async function startApp(): Promise<void> {
message: data.message, message: data.message,
// 'message' event: for 1:1 converations, the conversation is same as sender // 'message' event: for 1:1 converations, the conversation is same as sender
destination: data.source, destination: data.source,
destinationUuid: data.sourceUuid, destinationUuid: {
aci: data.sourceUuid,
},
}); });
const { PROFILE_KEY_UPDATE } = Proto.DataMessage.Flags; const { PROFILE_KEY_UPDATE } = Proto.DataMessage.Flags;
@ -2699,11 +2702,9 @@ export async function startApp(): Promise<void> {
result: SendStateByConversationId, result: SendStateByConversationId,
{ destinationUuid, destination, isAllowedToReplyToStory } { destinationUuid, destination, isAllowedToReplyToStory }
) => { ) => {
const conversation = window.ConversationController.lookupOrCreate({ const conversation = window.ConversationController.get(
uuid: destinationUuid, destinationUuid?.aci || destinationUuid?.pni || destination
e164: destination, );
reason: 'createSentMessage',
});
if (!conversation || conversation.id === ourId) { if (!conversation || conversation.id === ourId) {
return result; return result;
} }
@ -2729,7 +2730,12 @@ export async function startApp(): Promise<void> {
if (unidentifiedStatus.length) { if (unidentifiedStatus.length) {
unidentifiedDeliveries = unidentifiedStatus unidentifiedDeliveries = unidentifiedStatus
.filter(item => Boolean(item.unidentified)) .filter(item => Boolean(item.unidentified))
.map(item => item.destinationUuid || item.destination) .map(
item =>
item.destinationUuid?.aci ||
item.destinationUuid?.pni ||
item.destination
)
.filter(isNotNil); .filter(isNotNil);
} }
@ -2770,7 +2776,7 @@ export async function startApp(): Promise<void> {
}: { }: {
message: ProcessedDataMessage; message: ProcessedDataMessage;
destination?: string; destination?: string;
destinationUuid?: string; destinationUuid?: TaggedUUIDStringType;
}): MessageDescriptor => { }): MessageDescriptor => {
if (message.groupV2) { if (message.groupV2) {
const { id } = message.groupV2; const { id } = message.groupV2;
@ -2810,11 +2816,9 @@ export async function startApp(): Promise<void> {
}; };
} }
const conversation = window.ConversationController.lookupOrCreate({ const conversation = window.ConversationController.get(
uuid: destinationUuid, destinationUuid?.aci || destinationUuid?.pni || destination
e164: destination, );
reason: `getMessageDescriptor(${message.timestamp}): private`,
});
strictAssert(conversation, 'Destination conversation cannot be created'); strictAssert(conversation, 'Destination conversation cannot be created');
return { return {

View file

@ -82,7 +82,12 @@ const deleteStoryForEveryoneJobDataSchema = z.object({
updatedStoryRecipients: z updatedStoryRecipients: z
.array( .array(
z.object({ 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()), distributionListIds: z.array(z.string()),
isAllowedToReply: z.boolean(), isAllowedToReply: z.boolean(),
}) })

View file

@ -36,6 +36,7 @@ import { strictAssert } from '../../util/assert';
import type { LoggerType } from '../../types/Logging'; import type { LoggerType } from '../../types/Logging';
import { isStory } from '../../messages/helpers'; import { isStory } from '../../messages/helpers';
import { sendToGroup } from '../../util/sendToGroup'; import { sendToGroup } from '../../util/sendToGroup';
import { getTaggedConversationUuid } from '../../util/getConversationUuid';
export async function sendDeleteForEveryone( export async function sendDeleteForEveryone(
conversation: ConversationModel, conversation: ConversationModel,
@ -139,7 +140,9 @@ export async function sendDeleteForEveryone(
proto.dataMessage proto.dataMessage
).finish(), ).finish(),
destination: conversation.get('e164'), destination: conversation.get('e164'),
destinationUuid: conversation.get('uuid'), destinationUuid: getTaggedConversationUuid(
conversation.attributes
),
expirationStartTimestamp: null, expirationStartTimestamp: null,
options: sendOptions, options: sendOptions,
timestamp, timestamp,

View file

@ -239,8 +239,18 @@ export async function sendDeleteStoryForEveryone(
await handleMessageSend( await handleMessageSend(
messaging.sendSyncMessage({ messaging.sendSyncMessage({
destination: undefined, destination: undefined,
destinationUuid, destinationUuid: {
storyMessageRecipients: updatedStoryRecipients, aci: destinationUuid,
},
storyMessageRecipients: updatedStoryRecipients?.map(
({ destinationUuid: legacyDestinationUuid, ...rest }) => {
return {
// The field was renamed.
legacyDestinationUuid,
...rest,
};
}
),
expirationStartTimestamp: null, expirationStartTimestamp: null,
isUpdate: true, isUpdate: true,
options, options,

View file

@ -20,6 +20,7 @@ import { handleMessageSend } from '../../util/handleMessageSend';
import { isConversationAccepted } from '../../util/isConversationAccepted'; import { isConversationAccepted } from '../../util/isConversationAccepted';
import { isConversationUnregistered } from '../../util/isConversationUnregistered'; import { isConversationUnregistered } from '../../util/isConversationUnregistered';
import { DurationInSeconds } from '../../util/durations'; import { DurationInSeconds } from '../../util/durations';
import { getTaggedConversationUuid } from '../../util/getConversationUuid';
export async function sendDirectExpirationTimerUpdate( export async function sendDirectExpirationTimerUpdate(
conversation: ConversationModel, conversation: ConversationModel,
@ -107,7 +108,7 @@ export async function sendDirectExpirationTimerUpdate(
proto.dataMessage proto.dataMessage
).finish(), ).finish(),
destination: conversation.get('e164'), destination: conversation.get('e164'),
destinationUuid: conversation.get('uuid'), destinationUuid: getTaggedConversationUuid(conversation.attributes),
expirationStartTimestamp: null, expirationStartTimestamp: null,
options: sendOptions, options: sendOptions,
timestamp, timestamp,

View file

@ -34,6 +34,7 @@ import { handleMultipleSendErrors } from './handleMultipleSendErrors';
import { isGroupV2, isMe } from '../../util/whatTypeOfConversation'; import { isGroupV2, isMe } from '../../util/whatTypeOfConversation';
import { ourProfileKeyService } from '../../services/ourProfileKey'; import { ourProfileKeyService } from '../../services/ourProfileKey';
import { sendContentMessageToGroup } from '../../util/sendToGroup'; import { sendContentMessageToGroup } from '../../util/sendToGroup';
import { getTaggedConversationUuid } from '../../util/getConversationUuid';
import { distributionListToSendTarget } from '../../util/distributionListToSendTarget'; import { distributionListToSendTarget } from '../../util/distributionListToSendTarget';
import { uploadAttachment } from '../../util/uploadAttachment'; import { uploadAttachment } from '../../util/uploadAttachment';
import { SendMessageChallengeError } from '../../textsecure/Errors'; import { SendMessageChallengeError } from '../../textsecure/Errors';
@ -548,8 +549,17 @@ export async function sendStory(
// Build up the sync message's storyMessageRecipients and send it // Build up the sync message's storyMessageRecipients and send it
const storyMessageRecipients: StoryMessageRecipientsType = []; const storyMessageRecipients: StoryMessageRecipientsType = [];
recipientsByUuid.forEach((distributionListIds, destinationUuid) => { recipientsByUuid.forEach((distributionListIds, destinationUuid) => {
const recipient = window.ConversationController.get(destinationUuid);
if (!recipient) {
return;
}
const taggedUuid = getTaggedConversationUuid(recipient.attributes);
if (!taggedUuid) {
return;
}
storyMessageRecipients.push({ storyMessageRecipients.push({
destinationUuid, destinationAci: taggedUuid.aci,
destinationPni: taggedUuid.pni,
distributionListIds: Array.from(distributionListIds), distributionListIds: Array.from(distributionListIds),
isAllowedToReply: canReplyUuids.has(destinationUuid), isAllowedToReply: canReplyUuids.has(destinationUuid),
}); });
@ -567,7 +577,7 @@ export async function sendStory(
await messaging.sendSyncMessage({ await messaging.sendSyncMessage({
// Note: these two fields will be undefined if we're sending to a group // Note: these two fields will be undefined if we're sending to a group
destination: conversation.get('e164'), destination: conversation.get('e164'),
destinationUuid: conversation.get('uuid'), destinationUuid: getTaggedConversationUuid(conversation.attributes),
storyMessage: originalStoryMessage, storyMessage: originalStoryMessage,
storyMessageRecipients, storyMessageRecipients,
expirationStartTimestamp: null, expirationStartTimestamp: null,

View file

@ -49,6 +49,7 @@ import { SendMessageProtoError } from '../textsecure/Errors';
import * as expirationTimer from '../util/expirationTimer'; import * as expirationTimer from '../util/expirationTimer';
import { getUserLanguages } from '../util/userLanguages'; import { getUserLanguages } from '../util/userLanguages';
import { getMessageSentTimestamp } from '../util/getMessageSentTimestamp'; import { getMessageSentTimestamp } from '../util/getMessageSentTimestamp';
import { getTaggedConversationUuid } from '../util/getConversationUuid';
import type { ReactionType } from '../types/Reactions'; import type { ReactionType } from '../types/Reactions';
import { UUID, UUIDKind } from '../types/UUID'; import { UUID, UUIDKind } from '../types/UUID';
@ -1823,7 +1824,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
...encodedContent, ...encodedContent,
timestamp, timestamp,
destination: conv.get('e164'), destination: conv.get('e164'),
destinationUuid: conv.get('uuid'), destinationUuid: getTaggedConversationUuid(conv.attributes),
expirationStartTimestamp: expirationStartTimestamp:
this.get('expirationStartTimestamp') || null, this.get('expirationStartTimestamp') || null,
conversationIdsSentTo, conversationIdsSentTo,
@ -2221,14 +2222,16 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
unidentifiedStatus.forEach( unidentifiedStatus.forEach(
({ destinationUuid, destination, unidentified }) => { ({ destinationUuid, destination, unidentified }) => {
const identifier = destinationUuid || destination; const identifier =
destinationUuid?.aci || destinationUuid?.pni || destination;
if (!identifier) { if (!identifier) {
return; return;
} }
const { conversation: destinationConversation } = const { conversation: destinationConversation } =
window.ConversationController.maybeMergeContacts({ window.ConversationController.maybeMergeContacts({
aci: destinationUuid, aci: destinationUuid?.aci,
pni: destinationUuid?.pni,
e164: destination || undefined, e164: destination || undefined,
reason: `handleDataMessage(${initialMessage.timestamp})`, reason: `handleDataMessage(${initialMessage.timestamp})`,
}); });

View file

@ -2,21 +2,21 @@
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import { assert } from 'chai'; import { assert } from 'chai';
import getGuid from 'uuid/v4'; import { UUID } from '../types/UUID';
import { processSyncMessage } from '../textsecure/processSyncMessage'; import { processSyncMessage } from '../textsecure/processSyncMessage';
describe('processSyncMessage', () => { describe('processSyncMessage', () => {
it('should normalize UUIDs in sent', () => { const destinationUuid = UUID.generate().toString();
const destinationUuid = getGuid();
it('should normalize UUIDs in sent (aci)', () => {
const out = processSyncMessage({ const out = processSyncMessage({
sent: { sent: {
destinationUuid: destinationUuid.toUpperCase(), destinationAci: destinationUuid.toUpperCase(),
unidentifiedStatus: [ unidentifiedStatus: [
{ {
destinationUuid: destinationUuid.toUpperCase(), destinationAci: destinationUuid.toUpperCase(),
}, },
], ],
}, },
@ -24,11 +24,51 @@ describe('processSyncMessage', () => {
assert.deepStrictEqual(out, { assert.deepStrictEqual(out, {
sent: { 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: [ unidentifiedStatus: [
{ {
destinationUuid, destinationPni: destinationUuid.toUpperCase(),
},
],
},
});
assert.deepStrictEqual(out, {
sent: {
destinationUuid: {
aci: undefined,
pni: destinationUuid,
},
storyMessageRecipients: undefined,
unidentifiedStatus: [
{
destinationUuid: {
aci: undefined,
pni: destinationUuid,
},
}, },
], ],
}, },

View file

@ -52,7 +52,7 @@ import { bytesToUuid } from '../Crypto';
import type { DownloadedAttachmentType } from '../types/Attachment'; import type { DownloadedAttachmentType } from '../types/Attachment';
import { Address } from '../types/Address'; import { Address } from '../types/Address';
import { QualifiedAddress } from '../types/QualifiedAddress'; 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 { UUID, UUIDKind } from '../types/UUID';
import * as Errors from '../types/errors'; import * as Errors from '../types/errors';
@ -2028,8 +2028,9 @@ export default class MessageReceiver
let p: Promise<void> = Promise.resolve(); let p: Promise<void> = Promise.resolve();
if (msg.flags && msg.flags & Proto.DataMessage.Flags.END_SESSION) { if (msg.flags && msg.flags & Proto.DataMessage.Flags.END_SESSION) {
if (destinationUuid) { const anyUuid = destinationUuid?.aci ?? destinationUuid?.pni;
p = this.handleEndSession(envelope, new UUID(destinationUuid)); if (anyUuid) {
p = this.handleEndSession(envelope, new UUID(anyUuid));
} else if (destination) { } else if (destination) {
const theirUuid = UUID.lookup(destination); const theirUuid = UUID.lookup(destination);
if (theirUuid) { if (theirUuid) {
@ -2061,8 +2062,7 @@ export default class MessageReceiver
const ev = new SentEvent( const ev = new SentEvent(
{ {
destination: dropNull(destination), destination: dropNull(destination),
destinationUuid: destinationUuid,
dropNull(destinationUuid) || envelope.destinationUuid.toString(),
timestamp: timestamp?.toNumber(), timestamp: timestamp?.toNumber(),
serverTimestamp: envelope.serverTimestamp, serverTimestamp: envelope.serverTimestamp,
device: envelope.sourceDevice, device: envelope.sourceDevice,
@ -2168,7 +2168,9 @@ export default class MessageReceiver
if (sentMessage && message.groupV2) { if (sentMessage && message.groupV2) {
const ev = new SentEvent( const ev = new SentEvent(
{ {
destinationUuid: envelope.destinationUuid.toString(), destinationUuid: {
aci: envelope.destinationUuid.toString(),
},
device: envelope.sourceDevice, device: envelope.sourceDevice,
isRecipientUpdate: Boolean(sentMessage.isRecipientUpdate), isRecipientUpdate: Boolean(sentMessage.isRecipientUpdate),
message, message,
@ -2183,10 +2185,7 @@ export default class MessageReceiver
} }
return { return {
destinationUuid: normalizeUuid( destinationUuid,
destinationUuid,
'handleStoryMessage.destinationUuid'
),
isAllowedToReplyToStory: Boolean(isAllowedToReply), isAllowedToReplyToStory: Boolean(isAllowedToReply),
}; };
}) })
@ -2202,25 +2201,26 @@ export default class MessageReceiver
const { storyMessageRecipients } = sentMessage; const { storyMessageRecipients } = sentMessage;
const recipients = storyMessageRecipients ?? []; const recipients = storyMessageRecipients ?? [];
const isAllowedToReply = new Map<string, boolean>(); const isAllowedToReply = new Map<UUIDStringType, boolean>();
const distributionListToSentUuid = new Map<string, Set<string>>(); const distributionListToSentUuid = new Map<
string,
Map<UUIDStringType, TaggedUUIDStringType>
>();
recipients.forEach(recipient => { recipients.forEach(recipient => {
const { destinationUuid } = recipient; const { destinationUuid } = recipient;
if (!destinationUuid) { if (!destinationUuid?.aci && !destinationUuid?.pni) {
return; return;
} }
const normalizedDestinationUuid = normalizeUuid( const destinationUuidString =
destinationUuid, destinationUuid?.aci || destinationUuid?.pni;
'handleStoryMessage.destinationUuid'
);
if (recipient.distributionListIds) { if (recipient.distributionListIds) {
recipient.distributionListIds.forEach(listId => { recipient.distributionListIds.forEach(listId => {
const sentUuids: Set<string> = const sentUuids: Map<UUIDStringType, TaggedUUIDStringType> =
distributionListToSentUuid.get(listId) || new Set(); distributionListToSentUuid.get(listId) || new Map();
sentUuids.add(normalizedDestinationUuid); sentUuids.set(destinationUuidString, destinationUuid);
distributionListToSentUuid.set(listId, sentUuids); distributionListToSentUuid.set(listId, sentUuids);
}); });
} else { } else {
@ -2232,7 +2232,7 @@ export default class MessageReceiver
} }
isAllowedToReply.set( isAllowedToReply.set(
normalizedDestinationUuid, destinationUuidString,
recipient.isAllowedToReply !== false recipient.isAllowedToReply !== false
); );
}); });
@ -2240,14 +2240,22 @@ export default class MessageReceiver
distributionListToSentUuid.forEach((sentToUuids, listId) => { distributionListToSentUuid.forEach((sentToUuids, listId) => {
const ev = new SentEvent( const ev = new SentEvent(
{ {
destinationUuid: envelope.destinationUuid.toString(), destinationUuid: {
aci: envelope.destinationUuid.toString(),
pni: undefined,
},
timestamp: envelope.timestamp, timestamp: envelope.timestamp,
serverTimestamp: envelope.serverTimestamp, serverTimestamp: envelope.serverTimestamp,
device: envelope.sourceDevice, device: envelope.sourceDevice,
unidentifiedStatus: Array.from(sentToUuids).map( unidentifiedStatus: Array.from(sentToUuids.values()).map(
destinationUuid => ({ destinationUuid => ({
destinationUuid, destinationUuid,
isAllowedToReplyToStory: isAllowedToReply.get(destinationUuid), isAllowedToReplyToStory: Boolean(
(destinationUuid.aci &&
isAllowedToReply.get(destinationUuid.aci)) ||
(destinationUuid.pni &&
isAllowedToReply.get(destinationUuid.pni))
),
}) })
), ),
message, message,
@ -2867,11 +2875,15 @@ export default class MessageReceiver
return undefined; return undefined;
} }
private getDestination(sentMessage: Proto.SyncMessage.ISent) { private getDestination(sentMessage: ProcessedSent) {
if (sentMessage.message && sentMessage.message.groupV2) { if (sentMessage.message && sentMessage.message.groupV2) {
return `groupv2(${this.getGroupId(sentMessage.message)})`; return `groupv2(${this.getGroupId(sentMessage.message)})`;
} }
return sentMessage.destination || sentMessage.destinationUuid; return (
sentMessage.destination ||
sentMessage.destinationUuid?.aci ||
sentMessage.destinationUuid?.pni
);
} }
private async handleSyncMessage( private async handleSyncMessage(
@ -3067,8 +3079,7 @@ export default class MessageReceiver
const ev = new SentEvent( const ev = new SentEvent(
{ {
destination: dropNull(destination), destination: dropNull(destination),
destinationUuid: destinationUuid,
dropNull(destinationUuid) || envelope.destinationUuid.toString(),
timestamp: envelope.timestamp, timestamp: envelope.timestamp,
serverTimestamp: envelope.serverTimestamp, serverTimestamp: envelope.serverTimestamp,
device: envelope.sourceDevice, device: envelope.sourceDevice,

View file

@ -17,6 +17,7 @@ import type { ConversationModel } from '../models/conversations';
import { GLOBAL_ZONE } from '../SignalProtocolStore'; import { GLOBAL_ZONE } from '../SignalProtocolStore';
import { assertDev, strictAssert } from '../util/assert'; import { assertDev, strictAssert } from '../util/assert';
import { parseIntOrThrow } from '../util/parseIntOrThrow'; import { parseIntOrThrow } from '../util/parseIntOrThrow';
import { getTaggedConversationUuid } from '../util/getConversationUuid';
import { Address } from '../types/Address'; import { Address } from '../types/Address';
import { QualifiedAddress } from '../types/QualifiedAddress'; import { QualifiedAddress } from '../types/QualifiedAddress';
import { SenderKeys } from '../LibSignalStores'; import { SenderKeys } from '../LibSignalStores';
@ -24,7 +25,7 @@ import type {
TextAttachmentType, TextAttachmentType,
UploadedAttachmentType, UploadedAttachmentType,
} from '../types/Attachment'; } from '../types/Attachment';
import type { UUID } from '../types/UUID'; import type { UUID, TaggedUUIDStringType } from '../types/UUID';
import type { import type {
ChallengeType, ChallengeType,
GetGroupLogOptionsType, GetGroupLogOptionsType,
@ -1205,7 +1206,7 @@ export default class MessageSender {
encodedEditMessage?: Uint8Array; encodedEditMessage?: Uint8Array;
timestamp: number; timestamp: number;
destination: string | undefined; destination: string | undefined;
destinationUuid: string | null | undefined; destinationUuid: TaggedUUIDStringType | undefined;
expirationStartTimestamp: number | null; expirationStartTimestamp: number | null;
conversationIdsSentTo?: Iterable<string>; conversationIdsSentTo?: Iterable<string>;
conversationIdsWithSealedSender?: Set<string>; conversationIdsWithSealedSender?: Set<string>;
@ -1230,8 +1231,10 @@ export default class MessageSender {
if (destination) { if (destination) {
sentMessage.destination = destination; sentMessage.destination = destination;
} }
if (destinationUuid) { if (destinationUuid?.aci) {
sentMessage.destinationUuid = destinationUuid; sentMessage.destinationAci = destinationUuid.aci;
} else if (destinationUuid?.pni) {
sentMessage.destinationPni = destinationUuid.pni;
} }
if (expirationStartTimestamp) { if (expirationStartTimestamp) {
sentMessage.expirationStartTimestamp = Long.fromNumber( sentMessage.expirationStartTimestamp = Long.fromNumber(
@ -1262,9 +1265,11 @@ export default class MessageSender {
if (e164) { if (e164) {
status.destination = e164; status.destination = e164;
} }
const uuid = conv.get('uuid'); const taggedUuid = getTaggedConversationUuid(conv.attributes);
if (uuid) { if (taggedUuid?.aci) {
status.destinationUuid = uuid; status.destinationAci = taggedUuid.aci;
} else if (taggedUuid?.pni) {
status.destinationPni = taggedUuid.pni;
} }
} }
status.unidentified = status.unidentified =

View file

@ -3,7 +3,7 @@
import type { SignalService as Proto } from '../protobuf'; import type { SignalService as Proto } from '../protobuf';
import type { IncomingWebSocketRequest } from './WebsocketResources'; 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 { TextAttachmentType } from '../types/Attachment';
import type { GiftBadgeStates } from '../components/conversation/Message'; import type { GiftBadgeStates } from '../components/conversation/Message';
import type { MIMEType } from '../types/MIME'; import type { MIMEType } from '../types/MIME';
@ -222,18 +222,31 @@ export type ProcessedDataMessage = {
export type ProcessedUnidentifiedDeliveryStatus = Omit< export type ProcessedUnidentifiedDeliveryStatus = Omit<
Proto.SyncMessage.Sent.IUnidentifiedDeliveryStatus, Proto.SyncMessage.Sent.IUnidentifiedDeliveryStatus,
'destinationUuid' 'destinationAci' | 'destinationPni'
> & { > & {
destinationUuid?: string; destinationUuid?: TaggedUUIDStringType;
isAllowedToReplyToStory?: boolean; isAllowedToReplyToStory?: boolean;
}; };
export type ProcessedStoryMessageRecipient = Omit<
Proto.SyncMessage.Sent.IStoryMessageRecipient,
'destinationAci' | 'destinationPni'
> & {
destinationUuid?: TaggedUUIDStringType;
};
export type ProcessedSent = Omit< export type ProcessedSent = Omit<
Proto.SyncMessage.ISent, Proto.SyncMessage.ISent,
'destinationId' | 'unidentifiedStatus' | 'destinationId'
| 'unidentifiedStatus'
| 'storyMessageRecipients'
| 'destinationAci'
| 'destinationPni'
> & { > & {
destinationId?: string; destinationId?: string;
destinationUuid?: TaggedUUIDStringType;
unidentifiedStatus?: Array<ProcessedUnidentifiedDeliveryStatus>; unidentifiedStatus?: Array<ProcessedUnidentifiedDeliveryStatus>;
storyMessageRecipients?: Array<ProcessedStoryMessageRecipient>;
}; };
export type ProcessedSyncMessage = Omit<Proto.ISyncMessage, 'sent'> & { export type ProcessedSyncMessage = Omit<Proto.ISyncMessage, 'sent'> & {

View file

@ -5,7 +5,7 @@
import type { PublicKey } from '@signalapp/libsignal-client'; import type { PublicKey } from '@signalapp/libsignal-client';
import type { SignalService as Proto } from '../protobuf'; import type { SignalService as Proto } from '../protobuf';
import type { UUIDStringType } from '../types/UUID'; import type { UUIDStringType, TaggedUUIDStringType } from '../types/UUID';
import type { import type {
ProcessedEnvelope, ProcessedEnvelope,
ProcessedDataMessage, ProcessedDataMessage,
@ -193,7 +193,7 @@ export class RetryRequestEvent extends ConfirmableEvent {
export type SentEventData = Readonly<{ export type SentEventData = Readonly<{
destination?: string; destination?: string;
destinationUuid?: string; destinationUuid?: TaggedUUIDStringType;
timestamp?: number; timestamp?: number;
serverTimestamp?: number; serverTimestamp?: number;
device: number | undefined; device: number | undefined;

View file

@ -1,30 +1,50 @@
// Copyright 2021 Signal Messenger, LLC // Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only // 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 { normalizeUuid } from '../util/normalizeUuid';
import type { import type { ProcessedSent, ProcessedSyncMessage } from './Types.d';
ProcessedUnidentifiedDeliveryStatus, import type { TaggedUUIDStringType } from '../types/UUID';
ProcessedSent,
ProcessedSyncMessage,
} from './Types.d';
import UnidentifiedDeliveryStatus = Proto.SyncMessage.Sent.IUnidentifiedDeliveryStatus; type ProtoUUIDTriple = Readonly<{
destinationAci?: string | null;
destinationPni?: string | null;
}>;
function processUnidentifiedDeliveryStatus( function toTaggedUuid({
status: UnidentifiedDeliveryStatus destinationAci,
): ProcessedUnidentifiedDeliveryStatus { destinationPni,
const { destinationUuid } = status; }: 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 extends ProtoUUIDTriple>(
input: Input
): Omit<Input, keyof ProtoUUIDTriple> & {
destinationUuid?: TaggedUUIDStringType;
} {
const { destinationAci, destinationPni, ...remaining } = input;
return { return {
...status, ...remaining,
destinationUuid: destinationUuid destinationUuid: toTaggedUuid({
? normalizeUuid( destinationAci,
destinationUuid, destinationPni,
'syncMessage.sent.unidentifiedStatus.destinationUuid' }),
)
: undefined,
}; };
} }
@ -35,17 +55,26 @@ function processSent(
return undefined; return undefined;
} }
const { destinationUuid, unidentifiedStatus } = sent; const {
destinationAci,
destinationPni,
unidentifiedStatus,
storyMessageRecipients,
...remaining
} = sent;
return { return {
...sent, ...remaining,
destinationUuid: destinationUuid
? normalizeUuid(destinationUuid, 'syncMessage.sent.destinationUuid')
: undefined,
destinationUuid: toTaggedUuid({
destinationAci,
destinationPni,
}),
unidentifiedStatus: unidentifiedStatus unidentifiedStatus: unidentifiedStatus
? unidentifiedStatus.map(processUnidentifiedDeliveryStatus) ? unidentifiedStatus.map(processProtoWithDestinationUuid)
: undefined,
storyMessageRecipients: storyMessageRecipients
? storyMessageRecipients.map(processProtoWithDestinationUuid)
: undefined, : undefined,
}; };
} }

View file

@ -170,7 +170,8 @@ export enum ResolvedSendStatus {
} }
export type StoryMessageRecipientsType = Array<{ export type StoryMessageRecipientsType = Array<{
destinationUuid: string; destinationAci?: string;
destinationPni?: string;
distributionListIds: Array<string>; distributionListIds: Array<string>;
isAllowedToReply: boolean; isAllowedToReply: boolean;
}>; }>;

View file

@ -8,6 +8,17 @@ import { strictAssert } from '../util/assert';
export type UUIDStringType = export type UUIDStringType =
`${string}-${string}-${string}-${string}-${string}`; `${string}-${string}-${string}-${string}-${string}`;
export type TaggedUUIDStringType = Readonly<
| {
aci: UUIDStringType;
pni?: undefined;
}
| {
aci?: undefined;
pni: UUIDStringType;
}
>;
export enum UUIDKind { export enum UUIDKind {
ACI = 'ACI', ACI = 'ACI',
PNI = 'PNI', PNI = 'PNI',

View file

@ -21,6 +21,7 @@ import { getMessageById } from '../messages/getMessageById';
import { strictAssert } from './assert'; import { strictAssert } from './assert';
import { repeat, zipObject } from './iterables'; import { repeat, zipObject } from './iterables';
import { isOlderThan } from './timestamp'; import { isOlderThan } from './timestamp';
import { getTaggedConversationUuid } from './getConversationUuid';
export async function deleteStoryForEveryone( export async function deleteStoryForEveryone(
stories: ReadonlyArray<StoryDataType>, stories: ReadonlyArray<StoryDataType>,
@ -157,8 +158,17 @@ export async function deleteStoryForEveryone(
const newStoryMessageRecipients: StoryMessageRecipientsType = []; const newStoryMessageRecipients: StoryMessageRecipientsType = [];
newStoryRecipients.forEach((recipientData, destinationUuid) => { newStoryRecipients.forEach((recipientData, destinationUuid) => {
const recipient = window.ConversationController.get(destinationUuid);
if (!recipient) {
return;
}
const taggedUuid = getTaggedConversationUuid(recipient.attributes);
if (!taggedUuid) {
return;
}
newStoryMessageRecipients.push({ newStoryMessageRecipients.push({
destinationUuid, destinationAci: taggedUuid.aci,
destinationPni: taggedUuid.pni,
distributionListIds: Array.from(recipientData.distributionListIds), distributionListIds: Array.from(recipientData.distributionListIds),
isAllowedToReply: recipientData.isAllowedToReply, isAllowedToReply: recipientData.isAllowedToReply,
}); });

View file

@ -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;
}

View file

@ -57,12 +57,15 @@ export async function onStoryRecipientUpdate(
Set<string> Set<string>
>(); >();
data.storyMessageRecipients.forEach(item => { data.storyMessageRecipients.forEach(item => {
if (!item.destinationUuid) { const { destinationAci, destinationPni } = item;
const destinationId = destinationAci || destinationPni;
if (!destinationId) {
return; return;
} }
const convo = window.ConversationController.get( const convo = window.ConversationController.get(
normalizeUuid(item.destinationUuid, `${logId}.destinationUuid`) normalizeUuid(destinationId, `${logId}.destinationId`)
); );
if (!convo || !item.distributionListIds) { if (!convo || !item.distributionListIds) {

View file

@ -6,6 +6,7 @@ import * as log from '../logging/log';
import { SendMessageProtoError } from '../textsecure/Errors'; import { SendMessageProtoError } from '../textsecure/Errors';
import { getSendOptions } from './getSendOptions'; import { getSendOptions } from './getSendOptions';
import { handleMessageSend } from './handleMessageSend'; import { handleMessageSend } from './handleMessageSend';
import { getTaggedConversationUuid } from './getConversationUuid';
import type { CallbackResultType } from '../textsecure/Types.d'; import type { CallbackResultType } from '../textsecure/Types.d';
import type { ConversationModel } from '../models/conversations'; import type { ConversationModel } from '../models/conversations';
@ -77,7 +78,7 @@ export async function wrapWithSyncMessageSend({
await handleMessageSend( await handleMessageSend(
sender.sendSyncMessage({ sender.sendSyncMessage({
destination: conversation.get('e164'), destination: conversation.get('e164'),
destinationUuid: conversation.get('uuid'), destinationUuid: getTaggedConversationUuid(conversation.attributes),
encodedDataMessage: dataMessage, encodedDataMessage: dataMessage,
expirationStartTimestamp: null, expirationStartTimestamp: null,
options, options,