signal-desktop/ts/jobs/helpers/sendProfileKey.ts

190 lines
5.4 KiB
TypeScript
Raw Normal View History

2022-02-16 18:36:21 +00:00
// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { isNumber } from 'lodash';
import { handleMessageSend } from '../../util/handleMessageSend';
import { getSendOptions } from '../../util/getSendOptions';
import {
isDirectConversation,
isGroup,
2022-02-16 18:36:21 +00:00
isGroupV2,
} from '../../util/whatTypeOfConversation';
import { SignalService as Proto } from '../../protobuf';
import {
handleMultipleSendErrors,
maybeExpandErrors,
} from './handleMultipleSendErrors';
import { ourProfileKeyService } from '../../services/ourProfileKey';
import type { ConversationModel } from '../../models/conversations';
import type {
ConversationQueueJobBundle,
ProfileKeyJobData,
} from '../conversationJobQueue';
import type { CallbackResultType } from '../../textsecure/Types.d';
import { isConversationUnregistered } from '../../util/isConversationUnregistered';
import type { ConversationAttributesType } from '../../model-types.d';
import {
OutgoingIdentityKeyError,
SendMessageChallengeError,
SendMessageProtoError,
UnregisteredUserError,
} from '../../textsecure/Errors';
import { shouldSendToConversation } from './shouldSendToConversation';
2023-04-11 03:54:43 +00:00
import { sendToGroup } from '../../util/sendToGroup';
export function canAllErrorsBeIgnored(
conversation: ConversationAttributesType,
error: unknown
): boolean {
if (
error instanceof OutgoingIdentityKeyError ||
error instanceof SendMessageChallengeError ||
error instanceof UnregisteredUserError
) {
return true;
}
return Boolean(
isGroup(conversation) &&
error instanceof SendMessageProtoError &&
error.errors?.every(
item =>
item instanceof OutgoingIdentityKeyError ||
item instanceof SendMessageChallengeError ||
item instanceof UnregisteredUserError
)
);
}
2022-02-16 18:36:21 +00:00
// Note: because we don't have a recipient map, we will resend this message to folks that
// got it on the first go-round, if some sends fail. This is okay, because a recipient
// getting your profileKey again is just fine.
export async function sendProfileKey(
conversation: ConversationModel,
{
isFinalAttempt,
messaging,
2022-02-16 18:36:21 +00:00
shouldContinue,
timestamp,
timeRemaining,
log,
}: ConversationQueueJobBundle,
data: ProfileKeyJobData
): Promise<void> {
if (!shouldContinue) {
log.info('Ran out of time. Giving up on sending profile key');
return;
}
2024-05-31 00:44:06 +00:00
if (!data?.isOneTimeSend && !conversation.get('profileSharing')) {
2022-02-16 18:36:21 +00:00
log.info('No longer sharing profile. Cancelling job.');
return;
}
const profileKey = await ourProfileKeyService.get();
if (!profileKey) {
log.info('Unable to fetch profile. Cancelling job.');
return;
}
log.info(
2024-10-18 16:27:47 +00:00
`starting profile key share to ${conversation.idForLogging()} with timestamp ${timestamp} type=${data.type}`
2022-02-16 18:36:21 +00:00
);
const { revision } = data;
const sendOptions = await getSendOptions(conversation.attributes);
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
const contentHint = ContentHint.RESENDABLE;
const sendType = 'profileKeyUpdate';
let sendPromise: Promise<CallbackResultType>;
// Note: flags and the profileKey itself are all that matter in the proto.
if (!shouldSendToConversation(conversation, log)) {
return;
}
2022-02-16 18:36:21 +00:00
if (isDirectConversation(conversation.attributes)) {
if (isConversationUnregistered(conversation.attributes)) {
log.info(
`conversation ${conversation.idForLogging()} is unregistered; refusing to send`
);
return;
}
const proto = await messaging.getContentMessage({
2022-02-16 18:36:21 +00:00
flags: Proto.DataMessage.Flags.PROFILE_KEY_UPDATE,
profileKey,
recipients: conversation.getRecipients(),
expireTimerVersion: undefined,
2022-02-16 18:36:21 +00:00
timestamp,
2022-08-15 21:53:33 +00:00
includePniSignatureMessage: true,
2022-02-16 18:36:21 +00:00
});
sendPromise = messaging.sendIndividualProto({
2022-02-16 18:36:21 +00:00
contentHint,
serviceId: conversation.getSendTarget(),
2022-02-16 18:36:21 +00:00
options: sendOptions,
proto,
timestamp,
urgent: false,
2022-02-16 18:36:21 +00:00
});
} else {
if (isGroupV2(conversation.attributes) && !isNumber(revision)) {
log.error('No revision provided, but conversation is GroupV2');
}
const ourAci = window.textsecure.storage.user.getCheckedAci();
if (!conversation.hasMember(ourAci)) {
log.info(
`We are not part of group ${conversation.idForLogging()}; refusing to send`
);
return;
}
2022-02-16 18:36:21 +00:00
const groupV2Info = conversation.getGroupV2Info();
if (groupV2Info && isNumber(revision)) {
groupV2Info.revision = revision;
}
2023-04-11 03:54:43 +00:00
sendPromise = sendToGroup({
2022-02-16 18:36:21 +00:00
contentHint,
groupSendOptions: {
flags: Proto.DataMessage.Flags.PROFILE_KEY_UPDATE,
groupV2: groupV2Info,
profileKey,
timestamp,
},
messageId: undefined,
sendOptions,
sendTarget: conversation.toSenderKeyTarget(),
sendType,
urgent: false,
2022-02-16 18:36:21 +00:00
});
}
try {
await handleMessageSend(sendPromise, {
messageIds: [],
sendType,
});
} catch (error: unknown) {
if (canAllErrorsBeIgnored(conversation.attributes, error)) {
2022-02-16 18:36:21 +00:00
log.info(
2023-01-01 11:41:40 +00:00
'Group send failures were all OutgoingIdentityKeyError, SendMessageChallengeError, or UnregisteredUserError. Returning successfully.'
2022-02-16 18:36:21 +00:00
);
return;
}
await handleMultipleSendErrors({
errors: maybeExpandErrors(error),
isFinalAttempt,
log,
timeRemaining,
toThrow: error,
});
}
}