2021-05-07 01:15:25 +00:00
|
|
|
// Copyright 2021 Signal Messenger, LLC
|
|
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
|
|
|
|
import { ConversationAttributesType } from '../model-types.d';
|
|
|
|
import { handleMessageSend } from './handleMessageSend';
|
2021-07-15 23:48:09 +00:00
|
|
|
import { getSendOptions } from './getSendOptions';
|
2021-05-07 01:15:25 +00:00
|
|
|
import { sendReadReceiptsFor } from './sendReadReceiptsFor';
|
2021-06-17 17:15:10 +00:00
|
|
|
import { hasErrors } from '../state/selectors/message';
|
2021-07-15 23:48:09 +00:00
|
|
|
import { isNotNil } from './isNotNil';
|
2021-05-07 01:15:25 +00:00
|
|
|
|
|
|
|
export async function markConversationRead(
|
|
|
|
conversationAttrs: ConversationAttributesType,
|
|
|
|
newestUnreadId: number,
|
|
|
|
options: { readAt?: number; sendReadReceipts: boolean } = {
|
|
|
|
sendReadReceipts: true,
|
|
|
|
}
|
2021-05-10 18:49:13 +00:00
|
|
|
): Promise<boolean> {
|
2021-05-07 01:15:25 +00:00
|
|
|
const { id: conversationId } = conversationAttrs;
|
|
|
|
|
|
|
|
const [unreadMessages, unreadReactions] = await Promise.all([
|
|
|
|
window.Signal.Data.getUnreadByConversationAndMarkRead(
|
|
|
|
conversationId,
|
|
|
|
newestUnreadId,
|
|
|
|
options.readAt
|
|
|
|
),
|
|
|
|
window.Signal.Data.getUnreadReactionsAndMarkRead(
|
|
|
|
conversationId,
|
|
|
|
newestUnreadId
|
|
|
|
),
|
|
|
|
]);
|
|
|
|
|
2021-05-17 16:52:09 +00:00
|
|
|
window.log.info('markConversationRead', {
|
|
|
|
conversationId,
|
|
|
|
newestUnreadId,
|
|
|
|
unreadMessages: unreadMessages.length,
|
|
|
|
unreadReactions: unreadReactions.length,
|
|
|
|
});
|
|
|
|
|
2021-05-10 18:49:13 +00:00
|
|
|
if (!unreadMessages.length && !unreadReactions.length) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-05-17 16:52:09 +00:00
|
|
|
window.Whisper.Notifications.removeBy({ conversationId });
|
|
|
|
|
2021-05-07 01:15:25 +00:00
|
|
|
const unreadReactionSyncData = new Map<
|
|
|
|
string,
|
|
|
|
{
|
2021-07-15 23:48:09 +00:00
|
|
|
messageId?: string;
|
2021-05-07 01:15:25 +00:00
|
|
|
senderUuid?: string;
|
|
|
|
senderE164?: string;
|
|
|
|
timestamp: number;
|
|
|
|
}
|
|
|
|
>();
|
|
|
|
unreadReactions.forEach(reaction => {
|
|
|
|
const targetKey = `${reaction.targetAuthorUuid}/${reaction.targetTimestamp}`;
|
|
|
|
if (unreadReactionSyncData.has(targetKey)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
unreadReactionSyncData.set(targetKey, {
|
2021-07-15 23:48:09 +00:00
|
|
|
messageId: reaction.messageId,
|
2021-05-07 01:15:25 +00:00
|
|
|
senderE164: undefined,
|
|
|
|
senderUuid: reaction.targetAuthorUuid,
|
|
|
|
timestamp: reaction.targetTimestamp,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
const allReadMessagesSync = unreadMessages.map(messageSyncData => {
|
|
|
|
const message = window.MessageController.getById(messageSyncData.id);
|
|
|
|
// we update the in-memory MessageModel with the fresh database call data
|
|
|
|
if (message) {
|
|
|
|
message.set(messageSyncData);
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
2021-07-15 23:48:09 +00:00
|
|
|
messageId: messageSyncData.id,
|
2021-05-07 01:15:25 +00:00
|
|
|
senderE164: messageSyncData.source,
|
|
|
|
senderUuid: messageSyncData.sourceUuid,
|
|
|
|
senderId: window.ConversationController.ensureContactIds({
|
|
|
|
e164: messageSyncData.source,
|
|
|
|
uuid: messageSyncData.sourceUuid,
|
|
|
|
}),
|
|
|
|
timestamp: messageSyncData.sent_at,
|
2021-06-17 17:15:10 +00:00
|
|
|
hasErrors: message ? hasErrors(message.attributes) : false,
|
2021-05-07 01:15:25 +00:00
|
|
|
};
|
|
|
|
});
|
|
|
|
|
|
|
|
// Some messages we're marking read are local notifications with no sender
|
|
|
|
// If a message has errors, we don't want to send anything out about it.
|
|
|
|
// read syncs - let's wait for a client that really understands the message
|
|
|
|
// to mark it read. we'll mark our local error read locally, though.
|
|
|
|
// read receipts - here we can run into infinite loops, where each time the
|
|
|
|
// conversation is viewed, another error message shows up for the contact
|
2021-05-10 18:49:13 +00:00
|
|
|
const unreadMessagesSyncData = allReadMessagesSync.filter(
|
|
|
|
item => Boolean(item.senderId) && !item.hasErrors
|
2021-05-07 01:15:25 +00:00
|
|
|
);
|
|
|
|
|
2021-07-15 23:48:09 +00:00
|
|
|
const readSyncs: Array<{
|
|
|
|
messageId?: string;
|
|
|
|
senderE164?: string;
|
|
|
|
senderUuid?: string;
|
|
|
|
senderId?: string;
|
|
|
|
timestamp: number;
|
|
|
|
hasErrors?: string;
|
|
|
|
}> = [
|
2021-05-07 01:15:25 +00:00
|
|
|
...unreadMessagesSyncData,
|
|
|
|
...Array.from(unreadReactionSyncData.values()),
|
|
|
|
];
|
2021-07-15 23:48:09 +00:00
|
|
|
const messageIds = readSyncs.map(item => item.messageId).filter(isNotNil);
|
2021-05-07 01:15:25 +00:00
|
|
|
|
|
|
|
if (readSyncs.length && options.sendReadReceipts) {
|
|
|
|
window.log.info(`Sending ${readSyncs.length} read syncs`);
|
|
|
|
// Because syncReadMessages sends to our other devices, and sendReadReceipts goes
|
|
|
|
// to a contact, we need accessKeys for both.
|
2021-07-15 23:48:09 +00:00
|
|
|
const ourConversation = window.ConversationController.getOurConversationOrThrow();
|
|
|
|
const sendOptions = await getSendOptions(ourConversation.attributes, {
|
|
|
|
syncMessage: true,
|
|
|
|
});
|
|
|
|
|
|
|
|
if (window.ConversationController.areWePrimaryDevice()) {
|
|
|
|
window.log.warn(
|
|
|
|
'markConversationRead: We are primary device; not sending read syncs'
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
await handleMessageSend(
|
|
|
|
window.textsecure.messaging.syncReadMessages(readSyncs, sendOptions),
|
|
|
|
{ messageIds, sendType: 'readSync' }
|
|
|
|
);
|
|
|
|
}
|
2021-05-07 01:15:25 +00:00
|
|
|
|
|
|
|
await sendReadReceiptsFor(conversationAttrs, unreadMessagesSyncData);
|
|
|
|
}
|
|
|
|
|
2021-05-10 18:49:13 +00:00
|
|
|
return true;
|
2021-05-07 01:15:25 +00:00
|
|
|
}
|