2022-02-16 18:36:21 +00:00
|
|
|
// Copyright 2022 Signal Messenger, LLC
|
|
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
|
|
|
|
import { getSendOptionsForRecipients } from '../../util/getSendOptions';
|
|
|
|
import { isGroupV2 } from '../../util/whatTypeOfConversation';
|
|
|
|
import { SignalService as Proto } from '../../protobuf';
|
|
|
|
import {
|
|
|
|
handleMultipleSendErrors,
|
|
|
|
maybeExpandErrors,
|
|
|
|
} from './handleMultipleSendErrors';
|
|
|
|
import { wrapWithSyncMessageSend } from '../../util/wrapWithSyncMessageSend';
|
|
|
|
import * as Bytes from '../../Bytes';
|
|
|
|
import { strictAssert } from '../../util/assert';
|
2023-08-10 16:43:33 +00:00
|
|
|
import { isNotNil } from '../../util/isNotNil';
|
2022-02-16 18:36:21 +00:00
|
|
|
import { ourProfileKeyService } from '../../services/ourProfileKey';
|
|
|
|
|
|
|
|
import type { ConversationModel } from '../../models/conversations';
|
|
|
|
import type { GroupV2InfoType } from '../../textsecure/SendMessage';
|
|
|
|
import type {
|
|
|
|
GroupUpdateJobData,
|
|
|
|
ConversationQueueJobBundle,
|
|
|
|
} from '../conversationJobQueue';
|
2023-08-10 16:43:33 +00:00
|
|
|
import { getUntrustedConversationServiceIds } from './getUntrustedConversationServiceIds';
|
2023-04-11 03:54:43 +00:00
|
|
|
import { sendToGroup } from '../../util/sendToGroup';
|
2022-02-16 18:36:21 +00:00
|
|
|
|
|
|
|
// Note: because we don't have a recipient map, if some sends fail, we will resend this
|
|
|
|
// message to folks that got it on the first go-round. This is okay, because receivers
|
|
|
|
// will drop this as an empty message if they already know about its revision.
|
|
|
|
export async function sendGroupUpdate(
|
|
|
|
conversation: ConversationModel,
|
|
|
|
{
|
|
|
|
isFinalAttempt,
|
|
|
|
shouldContinue,
|
|
|
|
timeRemaining,
|
|
|
|
timestamp,
|
|
|
|
log,
|
|
|
|
}: ConversationQueueJobBundle,
|
|
|
|
data: GroupUpdateJobData
|
|
|
|
): Promise<void> {
|
2023-01-20 19:42:55 +00:00
|
|
|
const logId = `sendGroupUpdate/${conversation.idForLogging()}`;
|
|
|
|
|
2022-02-16 18:36:21 +00:00
|
|
|
if (!shouldContinue) {
|
2023-01-20 19:42:55 +00:00
|
|
|
log.info(`${logId}: Ran out of time. Giving up on sending group update`);
|
2022-02-16 18:36:21 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!isGroupV2(conversation.attributes)) {
|
|
|
|
log.error(
|
2023-01-20 19:42:55 +00:00
|
|
|
`${logId}: Conversation is not GroupV2, cannot send group update!`
|
2022-02-16 18:36:21 +00:00
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-01-20 19:42:55 +00:00
|
|
|
log.info(`${logId}: starting with timestamp ${timestamp}`);
|
|
|
|
|
|
|
|
const { groupChangeBase64, recipients: jobRecipients, revision } = data;
|
2022-02-16 18:36:21 +00:00
|
|
|
|
2023-08-10 16:43:33 +00:00
|
|
|
const recipients = jobRecipients
|
|
|
|
.map(id => {
|
|
|
|
const recipient = window.ConversationController.get(id);
|
|
|
|
if (!recipient) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
if (recipient.isUnregistered()) {
|
|
|
|
log.warn(
|
|
|
|
`${logId}: dropping unregistered recipient ${recipient.idForLogging()}`
|
|
|
|
);
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
if (recipient.isBlocked()) {
|
|
|
|
log.warn(
|
|
|
|
`${logId}: dropping blocked recipient ${recipient.idForLogging()}`
|
|
|
|
);
|
|
|
|
return undefined;
|
|
|
|
}
|
2023-01-20 19:42:55 +00:00
|
|
|
|
2023-08-10 16:43:33 +00:00
|
|
|
return recipient.getSendTarget();
|
|
|
|
})
|
|
|
|
.filter(isNotNil);
|
2022-02-16 18:36:21 +00:00
|
|
|
|
2023-08-10 16:43:33 +00:00
|
|
|
const untrustedServiceIds = getUntrustedConversationServiceIds(recipients);
|
|
|
|
if (untrustedServiceIds.length) {
|
2022-02-16 18:36:21 +00:00
|
|
|
window.reduxActions.conversations.conversationStoppedByMissingVerification({
|
|
|
|
conversationId: conversation.id,
|
2023-08-10 16:43:33 +00:00
|
|
|
untrustedServiceIds,
|
2022-02-16 18:36:21 +00:00
|
|
|
});
|
|
|
|
throw new Error(
|
2023-08-10 16:43:33 +00:00
|
|
|
`Group update blocked because ${untrustedServiceIds.length} conversation(s) were untrusted. Failing this attempt.`
|
2022-02-16 18:36:21 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
const sendOptions = await getSendOptionsForRecipients(recipients);
|
|
|
|
|
|
|
|
const { ContentHint } = Proto.UnidentifiedSenderMessage.Message;
|
|
|
|
const contentHint = ContentHint.RESENDABLE;
|
|
|
|
const sendType = 'groupChange';
|
|
|
|
|
|
|
|
const groupChange = groupChangeBase64
|
|
|
|
? Bytes.fromBase64(groupChangeBase64)
|
|
|
|
: undefined;
|
|
|
|
|
|
|
|
let profileKey: Uint8Array | undefined;
|
|
|
|
if (conversation.get('profileSharing')) {
|
|
|
|
profileKey = await ourProfileKeyService.get();
|
|
|
|
}
|
|
|
|
|
|
|
|
const groupV2Info = conversation.getGroupV2Info();
|
|
|
|
strictAssert(groupV2Info, 'groupV2Info missing');
|
|
|
|
const groupV2: GroupV2InfoType = {
|
|
|
|
...groupV2Info,
|
|
|
|
revision,
|
|
|
|
members: recipients,
|
|
|
|
groupChange,
|
|
|
|
};
|
|
|
|
|
|
|
|
try {
|
2022-05-23 22:08:13 +00:00
|
|
|
await conversation.queueJob(
|
|
|
|
'conversationQueue/sendGroupUpdate',
|
|
|
|
async abortSignal =>
|
|
|
|
wrapWithSyncMessageSend({
|
|
|
|
conversation,
|
|
|
|
logId,
|
|
|
|
messageIds: [],
|
|
|
|
send: async () =>
|
2023-04-11 03:54:43 +00:00
|
|
|
sendToGroup({
|
2022-05-23 22:08:13 +00:00
|
|
|
abortSignal,
|
|
|
|
groupSendOptions: {
|
|
|
|
groupV2,
|
|
|
|
timestamp,
|
|
|
|
profileKey,
|
|
|
|
},
|
|
|
|
contentHint,
|
|
|
|
messageId: undefined,
|
|
|
|
sendOptions,
|
|
|
|
sendTarget: conversation.toSenderKeyTarget(),
|
|
|
|
sendType,
|
2022-07-01 16:55:13 +00:00
|
|
|
urgent: false,
|
2022-05-23 22:08:13 +00:00
|
|
|
}),
|
|
|
|
sendType,
|
|
|
|
timestamp,
|
|
|
|
})
|
2022-02-16 18:36:21 +00:00
|
|
|
);
|
|
|
|
} catch (error: unknown) {
|
|
|
|
await handleMultipleSendErrors({
|
|
|
|
errors: maybeExpandErrors(error),
|
|
|
|
isFinalAttempt,
|
|
|
|
log,
|
|
|
|
timeRemaining,
|
|
|
|
toThrow: error,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|