diff --git a/ts/model-types.d.ts b/ts/model-types.d.ts index ca8c20240..16cfb43a5 100644 --- a/ts/model-types.d.ts +++ b/ts/model-types.d.ts @@ -116,7 +116,8 @@ export type MessageReactionType = { }; // Note: when adding to the set of things that can change via edits, sendNormalMessage.ts -// needs more usage of get/setPropForTimestamp. +// needs more usage of get/setPropForTimestamp. Also, these fields must match the fields +// in MessageAttributesType. export type EditHistoryType = { attachments?: Array; body?: string; @@ -126,6 +127,8 @@ export type EditHistoryType = { quote?: QuotedMessageType; sendStateByConversationId?: SendStateByConversationId; timestamp: number; + received_at: number; + received_at_ms?: number; }; export type MessageAttributesType = { @@ -152,6 +155,8 @@ export type MessageAttributesType = { isViewOnce?: boolean; editHistory?: Array; editMessageTimestamp?: number; + editMessageReceivedAt?: number; + editMessageReceivedAtMs?: number; key_changed?: string; local?: boolean; logger?: unknown; @@ -334,6 +339,8 @@ export type ConversationAttributesType = { lastMessagePrefix?: string; lastMessageAuthor?: string | null; lastMessageStatus?: LastMessageStatus | null; + lastMessageReceivedAt?: number; + lastMessageReceivedAtMs?: number; markedUnread?: boolean; messageCount?: number; messageCountBeforeMessageRequests?: number | null; diff --git a/ts/models/conversations.ts b/ts/models/conversations.ts index 1f21687d3..23884016b 100644 --- a/ts/models/conversations.ts +++ b/ts/models/conversations.ts @@ -4173,16 +4173,23 @@ export class ConversationModel extends window.Backbone return; } - const currentTimestamp = this.get('timestamp') || null; - - let timestamp = currentTimestamp; + let timestamp = this.get('timestamp') || null; + let lastMessageReceivedAt = this.get('lastMessageReceivedAt'); + let lastMessageReceivedAtMs = this.get('lastMessageReceivedAtMs'); if (activityMessage) { - const receivedAt = activityMessage.get('received_at_ms'); - timestamp = receivedAt - ? Math.min(activityMessage.get('sent_at'), receivedAt) - : activityMessage.get('sent_at'); + timestamp = + activityMessage.get('editMessageTimestamp') || + activityMessage.get('sent_at') || + timestamp; + lastMessageReceivedAt = + activityMessage.get('editMessageReceivedAt') || + activityMessage.get('received_at') || + lastMessageReceivedAt; + lastMessageReceivedAtMs = + activityMessage.get('editMessageReceivedAtMs') || + activityMessage.get('received_at_ms') || + lastMessageReceivedAtMs; } - timestamp = timestamp || currentTimestamp; const notificationData = previewMessage?.getNotificationData(); @@ -4196,6 +4203,8 @@ export class ConversationModel extends window.Backbone (previewMessage ? getMessagePropStatus(previewMessage.attributes, ourConversationId) : null) || null, + lastMessageReceivedAt, + lastMessageReceivedAtMs, timestamp, lastMessageDeletedForEveryone: previewMessage ? previewMessage.get('deletedForEveryone') diff --git a/ts/state/ducks/conversations.ts b/ts/state/ducks/conversations.ts index a342d5639..bbd85996e 100644 --- a/ts/state/ducks/conversations.ts +++ b/ts/state/ducks/conversations.ts @@ -282,6 +282,8 @@ export type ConversationType = ReadonlyDeep< isVerified?: boolean; activeAt?: number; timestamp?: number; + lastMessageReceivedAt?: number; + lastMessageReceivedAtMs?: number; inboxPosition?: number; left?: boolean; lastMessage?: LastMessageType; diff --git a/ts/state/selectors/conversations.ts b/ts/state/selectors/conversations.ts index 84028d4f9..45e2e697b 100644 --- a/ts/state/selectors/conversations.ts +++ b/ts/state/selectors/conversations.ts @@ -303,8 +303,9 @@ const collator = new Intl.Collator(); // phone numbers and contacts from scratch here again. export const _getConversationComparator = () => { return (left: ConversationType, right: ConversationType): number => { - const leftTimestamp = left.timestamp; - const rightTimestamp = right.timestamp; + // These two fields can be sorted with each other; they are timestamps + const leftTimestamp = left.lastMessageReceivedAtMs || left.timestamp; + const rightTimestamp = right.lastMessageReceivedAtMs || right.timestamp; if (leftTimestamp && !rightTimestamp) { return -1; } @@ -315,6 +316,19 @@ export const _getConversationComparator = () => { return rightTimestamp - leftTimestamp; } + // This field looks like a timestamp, but is actually a counter + const leftCounter = left.lastMessageReceivedAt; + const rightCounter = right.lastMessageReceivedAt; + if (leftCounter && !rightCounter) { + return -1; + } + if (rightCounter && !leftCounter) { + return 1; + } + if (leftCounter && rightCounter && leftCounter !== rightCounter) { + return rightCounter - leftCounter; + } + if ( typeof left.inboxPosition === 'number' && typeof right.inboxPosition === 'number' diff --git a/ts/test-electron/MessageReceipts_test.ts b/ts/test-electron/MessageReceipts_test.ts index 98537048e..fff784192 100644 --- a/ts/test-electron/MessageReceipts_test.ts +++ b/ts/test-electron/MessageReceipts_test.ts @@ -129,10 +129,14 @@ describe('MessageReceipts', () => { { sendStateByConversationId: defaultSendState, timestamp: editedSentAt, + received_at: 2, + received_at_ms: Date.now(), }, { sendStateByConversationId: defaultSendState, timestamp: sentAt, + received_at: 1, + received_at_ms: Date.now(), }, ], }; diff --git a/ts/util/getConversation.ts b/ts/util/getConversation.ts index 30d688ac6..9e06b7014 100644 --- a/ts/util/getConversation.ts +++ b/ts/util/getConversation.ts @@ -186,6 +186,8 @@ export function getConversation(model: ConversationModel): ConversationType { isVerified: model.isVerified(), isFetchingUUID: model.isFetchingUUID, lastMessage: getLastMessage(attributes), + lastMessageReceivedAt: attributes.lastMessageReceivedAt, + lastMessageReceivedAtMs: attributes.lastMessageReceivedAtMs, lastUpdated: dropNull(timestamp), left: Boolean(attributes.left), markedUnread: attributes.markedUnread, diff --git a/ts/util/handleEditMessage.ts b/ts/util/handleEditMessage.ts index b4d61c287..78d83abf5 100644 --- a/ts/util/handleEditMessage.ts +++ b/ts/util/handleEditMessage.ts @@ -121,6 +121,8 @@ export async function handleEditMessage( quote: mainMessage.quote, sendStateByConversationId: { ...mainMessage.sendStateByConversationId }, timestamp: mainMessage.timestamp, + received_at: mainMessage.received_at, + received_at_ms: mainMessage.received_at_ms, }, ]; @@ -253,6 +255,8 @@ export async function handleEditMessage( sendStateByConversationId: upgradedEditedMessageData.sendStateByConversationId, timestamp: upgradedEditedMessageData.timestamp, + received_at: upgradedEditedMessageData.received_at, + received_at_ms: upgradedEditedMessageData.received_at_ms, quote: nextEditedMessageQuote, }; @@ -268,6 +272,8 @@ export async function handleEditMessage( bodyRanges: editedMessage.bodyRanges, editHistory, editMessageTimestamp: upgradedEditedMessageData.timestamp, + editMessageReceivedAt: upgradedEditedMessageData.received_at, + editMessageReceivedAtMs: upgradedEditedMessageData.received_at_ms, preview: editedMessage.preview, quote: editedMessage.quote, });