Ensure that seenStatus is always updated along with readStatus
This commit is contained in:
parent
925b89b3a9
commit
e078a2ae54
7 changed files with 53 additions and 10 deletions
|
@ -3383,6 +3383,7 @@ export class ConversationModel extends window.Backbone
|
||||||
return this.queueJob('onReadMessage', () =>
|
return this.queueJob('onReadMessage', () =>
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
this.markRead(message.get('received_at')!, {
|
this.markRead(message.get('received_at')!, {
|
||||||
|
newestSentAt: message.get('sent_at'),
|
||||||
sendReadReceipts: false,
|
sendReadReceipts: false,
|
||||||
readAt,
|
readAt,
|
||||||
})
|
})
|
||||||
|
|
|
@ -210,7 +210,16 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
|
|
||||||
const readStatus = migrateLegacyReadStatus(this.attributes);
|
const readStatus = migrateLegacyReadStatus(this.attributes);
|
||||||
if (readStatus !== undefined) {
|
if (readStatus !== undefined) {
|
||||||
this.set('readStatus', readStatus, { silent: true });
|
this.set(
|
||||||
|
{
|
||||||
|
readStatus,
|
||||||
|
seenStatus:
|
||||||
|
readStatus === ReadStatus.Unread
|
||||||
|
? SeenStatus.Unseen
|
||||||
|
: SeenStatus.Seen,
|
||||||
|
},
|
||||||
|
{ silent: true }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const sendStateByConversationId = migrateLegacySendAttributes(
|
const sendStateByConversationId = migrateLegacySendAttributes(
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
import type { MessageAttributesType } from '../model-types.d';
|
import type { MessageAttributesType } from '../model-types.d';
|
||||||
import { ReadStatus, maxReadStatus } from '../messages/MessageReadStatus';
|
import { ReadStatus, maxReadStatus } from '../messages/MessageReadStatus';
|
||||||
import { notificationService } from './notifications';
|
import { notificationService } from './notifications';
|
||||||
|
import { SeenStatus } from '../MessageSeenStatus';
|
||||||
|
|
||||||
function markReadOrViewed(
|
function markReadOrViewed(
|
||||||
messageAttrs: Readonly<MessageAttributesType>,
|
messageAttrs: Readonly<MessageAttributesType>,
|
||||||
|
@ -17,6 +18,7 @@ function markReadOrViewed(
|
||||||
const nextMessageAttributes: MessageAttributesType = {
|
const nextMessageAttributes: MessageAttributesType = {
|
||||||
...messageAttrs,
|
...messageAttrs,
|
||||||
readStatus: newReadStatus,
|
readStatus: newReadStatus,
|
||||||
|
seenStatus: SeenStatus.Seen,
|
||||||
};
|
};
|
||||||
|
|
||||||
const { id: messageId, expireTimer, expirationStartTimestamp } = messageAttrs;
|
const { id: messageId, expireTimer, expirationStartTimestamp } = messageAttrs;
|
||||||
|
|
|
@ -21,6 +21,7 @@ import type { UUIDStringType } from '../types/UUID';
|
||||||
import type { BadgeType } from '../badges/types';
|
import type { BadgeType } from '../badges/types';
|
||||||
import type { RemoveAllConfiguration } from '../types/RemoveAllConfiguration';
|
import type { RemoveAllConfiguration } from '../types/RemoveAllConfiguration';
|
||||||
import type { LoggerType } from '../types/Logging';
|
import type { LoggerType } from '../types/Logging';
|
||||||
|
import type { ReadStatus } from '../messages/MessageReadStatus';
|
||||||
|
|
||||||
export type AttachmentDownloadJobTypeType =
|
export type AttachmentDownloadJobTypeType =
|
||||||
| 'long-message'
|
| 'long-message'
|
||||||
|
@ -397,7 +398,16 @@ export type DataInterface = {
|
||||||
storyId?: UUIDStringType;
|
storyId?: UUIDStringType;
|
||||||
}) => Promise<
|
}) => Promise<
|
||||||
Array<
|
Array<
|
||||||
Pick<MessageType, 'id' | 'source' | 'sourceUuid' | 'sent_at' | 'type'>
|
{ originalReadStatus: ReadStatus | undefined } & Pick<
|
||||||
|
MessageType,
|
||||||
|
| 'id'
|
||||||
|
| 'readStatus'
|
||||||
|
| 'seenStatus'
|
||||||
|
| 'sent_at'
|
||||||
|
| 'source'
|
||||||
|
| 'sourceUuid'
|
||||||
|
| 'type'
|
||||||
|
>
|
||||||
>
|
>
|
||||||
>;
|
>;
|
||||||
getUnreadReactionsAndMarkRead: (options: {
|
getUnreadReactionsAndMarkRead: (options: {
|
||||||
|
|
|
@ -2075,7 +2075,18 @@ async function getUnreadByConversationAndMarkRead({
|
||||||
storyId?: UUIDStringType;
|
storyId?: UUIDStringType;
|
||||||
readAt?: number;
|
readAt?: number;
|
||||||
}): Promise<
|
}): Promise<
|
||||||
Array<Pick<MessageType, 'id' | 'source' | 'sourceUuid' | 'sent_at' | 'type'>>
|
Array<
|
||||||
|
{ originalReadStatus: ReadStatus | undefined } & Pick<
|
||||||
|
MessageType,
|
||||||
|
| 'id'
|
||||||
|
| 'source'
|
||||||
|
| 'sourceUuid'
|
||||||
|
| 'sent_at'
|
||||||
|
| 'type'
|
||||||
|
| 'readStatus'
|
||||||
|
| 'seenStatus'
|
||||||
|
>
|
||||||
|
>
|
||||||
> {
|
> {
|
||||||
const db = getInstance();
|
const db = getInstance();
|
||||||
return db.transaction(() => {
|
return db.transaction(() => {
|
||||||
|
@ -2109,10 +2120,10 @@ async function getUnreadByConversationAndMarkRead({
|
||||||
.prepare<Query>(
|
.prepare<Query>(
|
||||||
`
|
`
|
||||||
SELECT id, json FROM messages
|
SELECT id, json FROM messages
|
||||||
INDEXED BY messages_unread
|
|
||||||
WHERE
|
WHERE
|
||||||
readStatus = ${ReadStatus.Unread} AND
|
|
||||||
conversationId = $conversationId AND
|
conversationId = $conversationId AND
|
||||||
|
seenStatus = ${SeenStatus.Unseen} AND
|
||||||
|
isStory = 0 AND
|
||||||
(${_storyIdPredicate(storyId, isGroup)}) AND
|
(${_storyIdPredicate(storyId, isGroup)}) AND
|
||||||
received_at <= $newestUnreadAt
|
received_at <= $newestUnreadAt
|
||||||
ORDER BY received_at DESC, sent_at DESC;
|
ORDER BY received_at DESC, sent_at DESC;
|
||||||
|
@ -2151,7 +2162,9 @@ async function getUnreadByConversationAndMarkRead({
|
||||||
return rows.map(row => {
|
return rows.map(row => {
|
||||||
const json = jsonToObject<MessageType>(row.json);
|
const json = jsonToObject<MessageType>(row.json);
|
||||||
return {
|
return {
|
||||||
|
originalReadStatus: json.readStatus,
|
||||||
readStatus: ReadStatus.Read,
|
readStatus: ReadStatus.Read,
|
||||||
|
seenStatus: SeenStatus.Seen,
|
||||||
...pick(json, [
|
...pick(json, [
|
||||||
'expirationStartTimestamp',
|
'expirationStartTimestamp',
|
||||||
'id',
|
'id',
|
||||||
|
|
|
@ -166,7 +166,7 @@ describe('sql/markRead', () => {
|
||||||
readAt,
|
readAt,
|
||||||
});
|
});
|
||||||
|
|
||||||
assert.lengthOf(markedRead2, 3, 'three messages marked read');
|
assert.lengthOf(markedRead2, 2, 'two messages marked read');
|
||||||
assert.strictEqual(markedRead2[0].id, message7.id, 'should be message7');
|
assert.strictEqual(markedRead2[0].id, message7.id, 'should be message7');
|
||||||
|
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
// Copyright 2021 Signal Messenger, LLC
|
// Copyright 2021 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import { omit } from 'lodash';
|
||||||
|
|
||||||
import type { ConversationAttributesType } from '../model-types.d';
|
import type { ConversationAttributesType } from '../model-types.d';
|
||||||
import { hasErrors } from '../state/selectors/message';
|
import { hasErrors } from '../state/selectors/message';
|
||||||
import { readReceiptsJobQueue } from '../jobs/readReceiptsJobQueue';
|
import { readReceiptsJobQueue } from '../jobs/readReceiptsJobQueue';
|
||||||
|
@ -9,6 +11,7 @@ import { notificationService } from '../services/notifications';
|
||||||
import { isGroup } from './whatTypeOfConversation';
|
import { isGroup } from './whatTypeOfConversation';
|
||||||
import * as log from '../logging/log';
|
import * as log from '../logging/log';
|
||||||
import { getConversationIdForLogging } from './idForLogging';
|
import { getConversationIdForLogging } from './idForLogging';
|
||||||
|
import { ReadStatus } from '../messages/MessageReadStatus';
|
||||||
|
|
||||||
export async function markConversationRead(
|
export async function markConversationRead(
|
||||||
conversationAttrs: ConversationAttributesType,
|
conversationAttrs: ConversationAttributesType,
|
||||||
|
@ -76,11 +79,12 @@ export async function markConversationRead(
|
||||||
const message = window.MessageController.getById(messageSyncData.id);
|
const message = window.MessageController.getById(messageSyncData.id);
|
||||||
// we update the in-memory MessageModel with the fresh database call data
|
// we update the in-memory MessageModel with the fresh database call data
|
||||||
if (message) {
|
if (message) {
|
||||||
message.set(messageSyncData);
|
message.set(omit(messageSyncData, 'originalReadStatus'));
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
messageId: messageSyncData.id,
|
messageId: messageSyncData.id,
|
||||||
|
originalReadStatus: messageSyncData.originalReadStatus,
|
||||||
senderE164: messageSyncData.source,
|
senderE164: messageSyncData.source,
|
||||||
senderUuid: messageSyncData.sourceUuid,
|
senderUuid: messageSyncData.sourceUuid,
|
||||||
senderId: window.ConversationController.ensureContactIds({
|
senderId: window.ConversationController.ensureContactIds({
|
||||||
|
@ -92,14 +96,18 @@ export async function markConversationRead(
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// Some messages we're marking read are local notifications with no sender
|
// Some messages we're marking read are local notifications with no sender or were just
|
||||||
// If a message has errors, we don't want to send anything out about it.
|
// unseen and not unread.
|
||||||
|
// Also, 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
|
// 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.
|
// 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
|
// read receipts - here we can run into infinite loops, where each time the
|
||||||
// conversation is viewed, another error message shows up for the contact
|
// conversation is viewed, another error message shows up for the contact
|
||||||
const unreadMessagesSyncData = allReadMessagesSync.filter(
|
const unreadMessagesSyncData = allReadMessagesSync.filter(
|
||||||
item => Boolean(item.senderId) && !item.hasErrors
|
item =>
|
||||||
|
Boolean(item.senderId) &&
|
||||||
|
item.originalReadStatus === ReadStatus.Unread &&
|
||||||
|
!item.hasErrors
|
||||||
);
|
);
|
||||||
|
|
||||||
const readSyncs: Array<{
|
const readSyncs: Array<{
|
||||||
|
|
Loading…
Reference in a new issue