// Copyright 2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import { handleMessageSend } from '../../util/handleMessageSend'; import { getSendOptions } from '../../util/getSendOptions'; import { isDirectConversation, isGroup, } from '../../util/whatTypeOfConversation'; import { SignalService as Proto } from '../../protobuf'; import { handleMultipleSendErrors, maybeExpandErrors, } from './handleMultipleSendErrors'; import type { ConversationModel } from '../../models/conversations'; import type { ConversationQueueJobBundle, CallingMessageJobData, } from '../conversationJobQueue'; import { isConversationUnregistered } from '../../util/isConversationUnregistered'; import { OutgoingIdentityKeyError, UnregisteredUserError, } from '../../textsecure/Errors'; import { getUntrustedConversationServiceIds } from './getUntrustedConversationServiceIds'; import { sendContentMessageToGroup } from '../../util/sendToGroup'; import * as Bytes from '../../Bytes'; import { getValidRecipients } from './getValidRecipients'; export async function sendCallingMessage( conversation: ConversationModel, { isFinalAttempt, messaging, shouldContinue, timestamp, timeRemaining, log, }: ConversationQueueJobBundle, data: CallingMessageJobData ): Promise { const logId = `sendCallingMessage(${conversation.idForLogging()}.${timestamp})`; if (!shouldContinue) { log.info(`${logId}: Ran out of time. Giving up.`); return; } log.info(`${logId}: Starting send`); if ( isDirectConversation(conversation.attributes) && isConversationUnregistered(conversation.attributes) ) { log.warn(`${logId}: Direct conversation is unregistered; refusing to send`); return; } const { protoBase64, urgent, recipients: jobRecipients, isPartialSend, groupId, } = data; const recipients = getValidRecipients( jobRecipients || conversation.getRecipients(), { log, logId } ); const untrustedServiceIds = getUntrustedConversationServiceIds(recipients); if (untrustedServiceIds.length) { window.reduxActions.conversations.conversationStoppedByMissingVerification({ conversationId: conversation.id, untrustedServiceIds, }); throw new Error( `${logId}: Blocked because ${untrustedServiceIds.length} conversation(s) were untrusted. Failing this attempt.` ); } if (recipients.length === 0) { log.warn(`${logId}: Giving up because there are no valid recipients.`); return; } const sendType = 'callingMessage'; const sendOptions = await getSendOptions(conversation.attributes, { groupId, }); const callingMessage = Proto.CallingMessage.decode( Bytes.fromBase64(protoBase64) ); const { ContentHint } = Proto.UnidentifiedSenderMessage.Message; try { if (isGroup(conversation.attributes)) { await handleMessageSend( sendContentMessageToGroup({ contentHint: ContentHint.DEFAULT, contentMessage: new Proto.Content({ callingMessage }), isPartialSend, messageId: undefined, recipients, sendOptions, sendTarget: conversation.toSenderKeyTarget(), sendType, timestamp, urgent, }), { messageIds: [], sendType } ); } else { const sendTarget = conversation.getSendTarget(); if (!sendTarget) { log.error(`${logId}: Direct conversation send target is falsy`); return; } await handleMessageSend( messaging.sendCallingMessage( sendTarget, callingMessage, timestamp, urgent, sendOptions ), { messageIds: [], sendType } ); } } catch (error: unknown) { if ( error instanceof OutgoingIdentityKeyError || error instanceof UnregisteredUserError ) { log.info( `${logId}: Send failure was OutgoingIdentityKeyError or UnregisteredUserError. Cancelling job.` ); return; } await handleMultipleSendErrors({ errors: maybeExpandErrors(error), isFinalAttempt, log, timeRemaining, toThrow: error, }); } }