Signal-Desktop/ts/util/sendReceipts.ts

140 lines
3.9 KiB
TypeScript

// Copyright 2021-2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { chunk } from 'lodash';
import type { LoggerType } from '../types/Logging';
import type { Receipt } from '../types/Receipt';
import { ReceiptType } from '../types/Receipt';
import { getSendOptions } from './getSendOptions';
import { handleMessageSend } from './handleMessageSend';
import { isConversationAccepted } from './isConversationAccepted';
import { isConversationUnregistered } from './isConversationUnregistered';
import { map } from './iterables';
import { missingCaseError } from './missingCaseError';
const CHUNK_SIZE = 100;
export async function sendReceipts({
log,
receipts,
type,
}: Readonly<{
log: LoggerType;
receipts: ReadonlyArray<Receipt>;
type: ReceiptType;
}>): Promise<void> {
let requiresUserSetting: boolean;
let methodName:
| 'sendDeliveryReceipt'
| 'sendReadReceipt'
| 'sendViewedReceipt';
switch (type) {
case ReceiptType.Delivery:
requiresUserSetting = false;
methodName = 'sendDeliveryReceipt';
break;
case ReceiptType.Read:
requiresUserSetting = true;
methodName = 'sendReadReceipt';
break;
case ReceiptType.Viewed:
requiresUserSetting = true;
methodName = 'sendViewedReceipt';
break;
default:
throw missingCaseError(type);
}
const { messaging } = window.textsecure;
if (!messaging) {
throw new Error('messaging is not available!');
}
if (requiresUserSetting && !window.storage.get('read-receipt-setting')) {
log.info('requires user setting. Not sending these receipts');
return;
}
const receiptsBySenderId: Map<string, Array<Receipt>> = receipts.reduce(
(result, receipt) => {
const { senderE164, senderUuid } = receipt;
if (!senderE164 && !senderUuid) {
log.error('no sender E164 or UUID. Skipping this receipt');
return result;
}
const senderId = window.ConversationController.ensureContactIds({
e164: senderE164,
uuid: senderUuid,
});
if (!senderId) {
throw new Error(
'no conversation found with that E164/UUID. Cannot send this receipt'
);
}
const existingGroup = result.get(senderId);
if (existingGroup) {
existingGroup.push(receipt);
} else {
result.set(senderId, [receipt]);
}
return result;
},
new Map()
);
await window.ConversationController.load();
await Promise.all(
map(receiptsBySenderId, async ([senderId, receiptsForSender]) => {
const sender = window.ConversationController.get(senderId);
if (!sender) {
throw new Error(
'despite having a conversation ID, no conversation was found'
);
}
if (!isConversationAccepted(sender.attributes)) {
log.info(
`conversation ${sender.idForLogging()} is not accepted; refusing to send`
);
return;
}
if (isConversationUnregistered(sender.attributes)) {
log.info(
`conversation ${sender.idForLogging()} is unregistered; refusing to send`
);
return;
}
if (sender.isBlocked()) {
log.info(
`conversation ${sender.idForLogging()} is blocked; refusing to send`
);
return;
}
const sendOptions = await getSendOptions(sender.attributes);
const batches = chunk(receiptsForSender, CHUNK_SIZE);
await Promise.all(
map(batches, async batch => {
const timestamps = batch.map(receipt => receipt.timestamp);
const messageIds = batch.map(receipt => receipt.messageId);
await handleMessageSend(
messaging[methodName]({
senderE164: sender.get('e164'),
senderUuid: sender.get('uuid'),
timestamps,
options: sendOptions,
}),
{ messageIds, sendType: type }
);
})
);
})
);
}