Use correct timestamp for receipts of edited messages
This commit is contained in:
parent
8fe0047822
commit
5869717cd3
21 changed files with 156 additions and 52 deletions
|
@ -15,6 +15,7 @@ import { SignalService as Proto } from '../../protobuf';
|
||||||
import { handleMessageSend } from '../../util/handleMessageSend';
|
import { handleMessageSend } from '../../util/handleMessageSend';
|
||||||
import { findAndFormatContact } from '../../util/findAndFormatContact';
|
import { findAndFormatContact } from '../../util/findAndFormatContact';
|
||||||
import { uploadAttachment } from '../../util/uploadAttachment';
|
import { uploadAttachment } from '../../util/uploadAttachment';
|
||||||
|
import { getMessageSentTimestamp } from '../../util/getMessageSentTimestamp';
|
||||||
import type { CallbackResultType } from '../../textsecure/Types.d';
|
import type { CallbackResultType } from '../../textsecure/Types.d';
|
||||||
import { isSent } from '../../messages/MessageSendState';
|
import { isSent } from '../../messages/MessageSendState';
|
||||||
import { isOutgoing, canReact } from '../../state/selectors/message';
|
import { isOutgoing, canReact } from '../../state/selectors/message';
|
||||||
|
@ -499,22 +500,11 @@ async function getMessageSendData({
|
||||||
storyContext?: StoryContextType;
|
storyContext?: StoryContextType;
|
||||||
}> {
|
}> {
|
||||||
const editMessageTimestamp = message.get('editMessageTimestamp');
|
const editMessageTimestamp = message.get('editMessageTimestamp');
|
||||||
const sentAt = message.get('sent_at');
|
|
||||||
const timestamp = message.get('timestamp');
|
|
||||||
|
|
||||||
let mainMessageTimestamp: number;
|
|
||||||
if (sentAt) {
|
|
||||||
mainMessageTimestamp = sentAt;
|
|
||||||
} else if (timestamp) {
|
|
||||||
log.error('message lacked sent_at. Falling back to timestamp');
|
|
||||||
mainMessageTimestamp = timestamp;
|
|
||||||
} else {
|
|
||||||
log.error(
|
|
||||||
'message lacked sent_at and timestamp. Falling back to current time'
|
|
||||||
);
|
|
||||||
mainMessageTimestamp = Date.now();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
const mainMessageTimestamp = getMessageSentTimestamp(message.attributes, {
|
||||||
|
includeEdits: false,
|
||||||
|
log,
|
||||||
|
});
|
||||||
const messageTimestamp = editMessageTimestamp || mainMessageTimestamp;
|
const messageTimestamp = editMessageTimestamp || mainMessageTimestamp;
|
||||||
|
|
||||||
const storyId = message.get('storyId');
|
const storyId = message.get('storyId');
|
||||||
|
|
|
@ -10,6 +10,7 @@ import * as log from '../logging/log';
|
||||||
import * as Errors from '../types/errors';
|
import * as Errors from '../types/errors';
|
||||||
import { deleteForEveryone } from '../util/deleteForEveryone';
|
import { deleteForEveryone } from '../util/deleteForEveryone';
|
||||||
import { drop } from '../util/drop';
|
import { drop } from '../util/drop';
|
||||||
|
import { getMessageSentTimestampSet } from '../util/getMessageSentTimestampSet';
|
||||||
|
|
||||||
export type DeleteAttributesType = {
|
export type DeleteAttributesType = {
|
||||||
targetSentTimestamp: number;
|
targetSentTimestamp: number;
|
||||||
|
@ -31,10 +32,11 @@ export class Deletes extends Collection<DeleteModel> {
|
||||||
}
|
}
|
||||||
|
|
||||||
forMessage(message: MessageModel): Array<DeleteModel> {
|
forMessage(message: MessageModel): Array<DeleteModel> {
|
||||||
|
const sentTimestamps = getMessageSentTimestampSet(message.attributes);
|
||||||
const matchingDeletes = this.filter(item => {
|
const matchingDeletes = this.filter(item => {
|
||||||
return (
|
return (
|
||||||
item.get('targetSentTimestamp') === message.get('sent_at') &&
|
item.get('fromId') === getContactId(message.attributes) &&
|
||||||
item.get('fromId') === getContactId(message.attributes)
|
sentTimestamps.has(item.get('targetSentTimestamp'))
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { drop } from '../util/drop';
|
||||||
import { filter, size } from '../util/iterables';
|
import { filter, size } from '../util/iterables';
|
||||||
import { getContactId } from '../messages/helpers';
|
import { getContactId } from '../messages/helpers';
|
||||||
import { handleEditMessage } from '../util/handleEditMessage';
|
import { handleEditMessage } from '../util/handleEditMessage';
|
||||||
|
import { getMessageSentTimestamp } from '../util/getMessageSentTimestamp';
|
||||||
|
|
||||||
export type EditAttributesType = {
|
export type EditAttributesType = {
|
||||||
conversationId: string;
|
conversationId: string;
|
||||||
|
@ -20,9 +21,10 @@ export type EditAttributesType = {
|
||||||
const edits = new Set<EditAttributesType>();
|
const edits = new Set<EditAttributesType>();
|
||||||
|
|
||||||
export function forMessage(message: MessageModel): Array<EditAttributesType> {
|
export function forMessage(message: MessageModel): Array<EditAttributesType> {
|
||||||
|
const sentAt = getMessageSentTimestamp(message.attributes, { log });
|
||||||
const matchingEdits = filter(edits, item => {
|
const matchingEdits = filter(edits, item => {
|
||||||
return (
|
return (
|
||||||
item.targetSentTimestamp === message.get('sent_at') &&
|
item.targetSentTimestamp === sentAt &&
|
||||||
item.fromId === getContactId(message.attributes)
|
item.fromId === getContactId(message.attributes)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -24,6 +24,7 @@ import dataInterface from '../sql/Client';
|
||||||
import * as log from '../logging/log';
|
import * as log from '../logging/log';
|
||||||
import { getSourceUuid } from '../messages/helpers';
|
import { getSourceUuid } from '../messages/helpers';
|
||||||
import { queueUpdateMessage } from '../util/messageBatcher';
|
import { queueUpdateMessage } from '../util/messageBatcher';
|
||||||
|
import { getMessageSentTimestamp } from '../util/getMessageSentTimestamp';
|
||||||
|
|
||||||
const { deleteSentProtoRecipient } = dataInterface;
|
const { deleteSentProtoRecipient } = dataInterface;
|
||||||
|
|
||||||
|
@ -159,7 +160,7 @@ export class MessageReceipts extends Collection<MessageReceiptModel> {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const sentAt = message.get('sent_at');
|
const sentAt = getMessageSentTimestamp(message.attributes, { log });
|
||||||
const receipts = this.filter(
|
const receipts = this.filter(
|
||||||
receipt => receipt.get('messageSentAt') === sentAt
|
receipt => receipt.get('messageSentAt') === sentAt
|
||||||
);
|
);
|
||||||
|
|
|
@ -16,6 +16,7 @@ import { getContactId, getContact } from '../messages/helpers';
|
||||||
import { isDirectConversation, isMe } from '../util/whatTypeOfConversation';
|
import { isDirectConversation, isMe } from '../util/whatTypeOfConversation';
|
||||||
import { isOutgoing, isStory } from '../state/selectors/message';
|
import { isOutgoing, isStory } from '../state/selectors/message';
|
||||||
import { strictAssert } from '../util/assert';
|
import { strictAssert } from '../util/assert';
|
||||||
|
import { getMessageSentTimestampSet } from '../util/getMessageSentTimestampSet';
|
||||||
|
|
||||||
export class ReactionModel extends Model<ReactionAttributesType> {}
|
export class ReactionModel extends Model<ReactionAttributesType> {}
|
||||||
|
|
||||||
|
@ -31,9 +32,10 @@ export class Reactions extends Collection<ReactionModel> {
|
||||||
}
|
}
|
||||||
|
|
||||||
forMessage(message: MessageModel): Array<ReactionModel> {
|
forMessage(message: MessageModel): Array<ReactionModel> {
|
||||||
|
const sentTimestamps = getMessageSentTimestampSet(message.attributes);
|
||||||
if (isOutgoing(message.attributes)) {
|
if (isOutgoing(message.attributes)) {
|
||||||
const outgoingReactions = this.filter(
|
const outgoingReactions = this.filter(item =>
|
||||||
item => item.get('targetTimestamp') === message.get('sent_at')
|
sentTimestamps.has(item.get('targetTimestamp'))
|
||||||
);
|
);
|
||||||
|
|
||||||
if (outgoingReactions.length > 0) {
|
if (outgoingReactions.length > 0) {
|
||||||
|
@ -44,14 +46,15 @@ export class Reactions extends Collection<ReactionModel> {
|
||||||
}
|
}
|
||||||
|
|
||||||
const senderId = getContactId(message.attributes);
|
const senderId = getContactId(message.attributes);
|
||||||
const sentAt = message.get('sent_at');
|
|
||||||
const reactionsBySource = this.filter(re => {
|
const reactionsBySource = this.filter(re => {
|
||||||
const targetSender = window.ConversationController.lookupOrCreate({
|
const targetSender = window.ConversationController.lookupOrCreate({
|
||||||
uuid: re.get('targetAuthorUuid'),
|
uuid: re.get('targetAuthorUuid'),
|
||||||
reason: 'Reactions.forMessage',
|
reason: 'Reactions.forMessage',
|
||||||
});
|
});
|
||||||
const targetTimestamp = re.get('targetTimestamp');
|
const targetTimestamp = re.get('targetTimestamp');
|
||||||
return targetSender?.id === senderId && targetTimestamp === sentAt;
|
return (
|
||||||
|
targetSender?.id === senderId && sentTimestamps.has(targetTimestamp)
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (reactionsBySource.length > 0) {
|
if (reactionsBySource.length > 0) {
|
||||||
|
|
|
@ -13,6 +13,7 @@ import * as log from '../logging/log';
|
||||||
import * as Errors from '../types/errors';
|
import * as Errors from '../types/errors';
|
||||||
import { StartupQueue } from '../util/StartupQueue';
|
import { StartupQueue } from '../util/StartupQueue';
|
||||||
import { queueUpdateMessage } from '../util/messageBatcher';
|
import { queueUpdateMessage } from '../util/messageBatcher';
|
||||||
|
import { getMessageSentTimestamp } from '../util/getMessageSentTimestamp';
|
||||||
|
|
||||||
export type ReadSyncAttributesType = {
|
export type ReadSyncAttributesType = {
|
||||||
senderId: string;
|
senderId: string;
|
||||||
|
@ -66,10 +67,13 @@ export class ReadSyncs extends Collection {
|
||||||
uuid: message.get('sourceUuid'),
|
uuid: message.get('sourceUuid'),
|
||||||
reason: 'ReadSyncs.forMessage',
|
reason: 'ReadSyncs.forMessage',
|
||||||
});
|
});
|
||||||
|
const messageTimestamp = getMessageSentTimestamp(message.attributes, {
|
||||||
|
log,
|
||||||
|
});
|
||||||
const sync = this.find(item => {
|
const sync = this.find(item => {
|
||||||
return (
|
return (
|
||||||
item.get('senderId') === sender?.id &&
|
item.get('senderId') === sender?.id &&
|
||||||
item.get('timestamp') === message.get('sent_at')
|
item.get('timestamp') === messageTimestamp
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
if (sync) {
|
if (sync) {
|
||||||
|
|
|
@ -16,6 +16,7 @@ import { queueAttachmentDownloads } from '../util/queueAttachmentDownloads';
|
||||||
import * as log from '../logging/log';
|
import * as log from '../logging/log';
|
||||||
import { GiftBadgeStates } from '../components/conversation/Message';
|
import { GiftBadgeStates } from '../components/conversation/Message';
|
||||||
import { queueUpdateMessage } from '../util/messageBatcher';
|
import { queueUpdateMessage } from '../util/messageBatcher';
|
||||||
|
import { getMessageSentTimestamp } from '../util/getMessageSentTimestamp';
|
||||||
|
|
||||||
export type ViewSyncAttributesType = {
|
export type ViewSyncAttributesType = {
|
||||||
senderId: string;
|
senderId: string;
|
||||||
|
@ -44,17 +45,18 @@ export class ViewSyncs extends Collection {
|
||||||
uuid: message.get('sourceUuid'),
|
uuid: message.get('sourceUuid'),
|
||||||
reason: 'ViewSyncs.forMessage',
|
reason: 'ViewSyncs.forMessage',
|
||||||
});
|
});
|
||||||
|
const messageTimestamp = getMessageSentTimestamp(message.attributes, {
|
||||||
|
log,
|
||||||
|
});
|
||||||
const syncs = this.filter(item => {
|
const syncs = this.filter(item => {
|
||||||
return (
|
return (
|
||||||
item.get('senderId') === sender?.id &&
|
item.get('senderId') === sender?.id &&
|
||||||
item.get('timestamp') === message.get('sent_at')
|
item.get('timestamp') === messageTimestamp
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
if (syncs.length) {
|
if (syncs.length) {
|
||||||
log.info(
|
log.info(
|
||||||
`Found ${syncs.length} early view sync(s) for message ${message.get(
|
`Found ${syncs.length} early view sync(s) for message ${messageTimestamp}`
|
||||||
'sent_at'
|
|
||||||
)}`
|
|
||||||
);
|
);
|
||||||
this.remove(syncs);
|
this.remove(syncs);
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ import { memoizeByThis } from '../util/memoizeByThis';
|
||||||
import { getInitials } from '../util/getInitials';
|
import { getInitials } from '../util/getInitials';
|
||||||
import { normalizeUuid } from '../util/normalizeUuid';
|
import { normalizeUuid } from '../util/normalizeUuid';
|
||||||
import { clearTimeoutIfNecessary } from '../util/clearTimeoutIfNecessary';
|
import { clearTimeoutIfNecessary } from '../util/clearTimeoutIfNecessary';
|
||||||
|
import { getMessageSentTimestamp } from '../util/getMessageSentTimestamp';
|
||||||
import type { AttachmentType, ThumbnailType } from '../types/Attachment';
|
import type { AttachmentType, ThumbnailType } from '../types/Attachment';
|
||||||
import { toDayMillis } from '../util/timestamp';
|
import { toDayMillis } from '../util/timestamp';
|
||||||
import { isVoiceMessage } from '../types/Attachment';
|
import { isVoiceMessage } from '../types/Attachment';
|
||||||
|
@ -2321,7 +2322,7 @@ export class ConversationModel extends window.Backbone
|
||||||
conversationId,
|
conversationId,
|
||||||
senderE164: m.source,
|
senderE164: m.source,
|
||||||
senderUuid: m.sourceUuid,
|
senderUuid: m.sourceUuid,
|
||||||
timestamp: m.sent_at,
|
timestamp: getMessageSentTimestamp(m, { log }),
|
||||||
isDirectConversation: isDirectConversation(this.attributes),
|
isDirectConversation: isDirectConversation(this.attributes),
|
||||||
})),
|
})),
|
||||||
});
|
});
|
||||||
|
|
|
@ -48,6 +48,7 @@ import type {
|
||||||
import { SendMessageProtoError } from '../textsecure/Errors';
|
import { SendMessageProtoError } from '../textsecure/Errors';
|
||||||
import * as expirationTimer from '../util/expirationTimer';
|
import * as expirationTimer from '../util/expirationTimer';
|
||||||
import { getUserLanguages } from '../util/userLanguages';
|
import { getUserLanguages } from '../util/userLanguages';
|
||||||
|
import { getMessageSentTimestamp } from '../util/getMessageSentTimestamp';
|
||||||
|
|
||||||
import type { ReactionType } from '../types/Reactions';
|
import type { ReactionType } from '../types/Reactions';
|
||||||
import { UUID, UUIDKind } from '../types/UUID';
|
import { UUID, UUIDKind } from '../types/UUID';
|
||||||
|
@ -1802,9 +1803,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
);
|
);
|
||||||
|
|
||||||
const isEditedMessage = Boolean(this.get('editHistory'));
|
const isEditedMessage = Boolean(this.get('editHistory'));
|
||||||
const mainMessageTimestamp = this.get('sent_at') || this.get('timestamp');
|
const timestamp = getMessageSentTimestamp(this.attributes, { log });
|
||||||
const timestamp =
|
|
||||||
this.get('editMessageTimestamp') || mainMessageTimestamp;
|
|
||||||
|
|
||||||
const encodedContent = isEditedMessage
|
const encodedContent = isEditedMessage
|
||||||
? {
|
? {
|
||||||
|
|
|
@ -9,8 +9,10 @@ import { strictAssert } from '../util/assert';
|
||||||
import { isDirectConversation } from '../util/whatTypeOfConversation';
|
import { isDirectConversation } from '../util/whatTypeOfConversation';
|
||||||
import { incrementMessageCounter } from '../util/incrementMessageCounter';
|
import { incrementMessageCounter } from '../util/incrementMessageCounter';
|
||||||
import { repeat, zipObject } from '../util/iterables';
|
import { repeat, zipObject } from '../util/iterables';
|
||||||
|
import { getMessageSentTimestamp } from '../util/getMessageSentTimestamp';
|
||||||
import { SendStatus } from '../messages/MessageSendState';
|
import { SendStatus } from '../messages/MessageSendState';
|
||||||
import { UUID } from '../types/UUID';
|
import { UUID } from '../types/UUID';
|
||||||
|
import * as log from '../logging/log';
|
||||||
|
|
||||||
export async function enqueueReactionForSend({
|
export async function enqueueReactionForSend({
|
||||||
emoji,
|
emoji,
|
||||||
|
@ -30,7 +32,9 @@ export async function enqueueReactionForSend({
|
||||||
`enqueueReactionForSend: message ${message.idForLogging()} had no source UUID`
|
`enqueueReactionForSend: message ${message.idForLogging()} had no source UUID`
|
||||||
);
|
);
|
||||||
|
|
||||||
const targetTimestamp = message.get('sent_at') || message.get('timestamp');
|
const targetTimestamp = getMessageSentTimestamp(message.attributes, {
|
||||||
|
log,
|
||||||
|
});
|
||||||
strictAssert(
|
strictAssert(
|
||||||
targetTimestamp,
|
targetTimestamp,
|
||||||
`enqueueReactionForSend: message ${message.idForLogging()} had no timestamp`
|
`enqueueReactionForSend: message ${message.idForLogging()} had no timestamp`
|
||||||
|
|
|
@ -389,7 +389,7 @@ export type FTSOptimizationStateType = Readonly<{
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export type EditedMessageType = Readonly<{
|
export type EditedMessageType = Readonly<{
|
||||||
fromId: string;
|
conversationId: string;
|
||||||
messageId: string;
|
messageId: string;
|
||||||
sentAt: number;
|
sentAt: number;
|
||||||
readStatus: MessageType['readStatus'];
|
readStatus: MessageType['readStatus'];
|
||||||
|
@ -523,7 +523,7 @@ export type DataInterface = {
|
||||||
storyId?: string;
|
storyId?: string;
|
||||||
}) => Promise<GetUnreadByConversationAndMarkReadResultType>;
|
}) => Promise<GetUnreadByConversationAndMarkReadResultType>;
|
||||||
getUnreadEditedMessagesAndMarkRead: (options: {
|
getUnreadEditedMessagesAndMarkRead: (options: {
|
||||||
fromId: string;
|
conversationId: string;
|
||||||
newestUnreadAt: number;
|
newestUnreadAt: number;
|
||||||
}) => Promise<GetUnreadByConversationAndMarkReadResultType>;
|
}) => Promise<GetUnreadByConversationAndMarkReadResultType>;
|
||||||
getUnreadReactionsAndMarkRead: (options: {
|
getUnreadReactionsAndMarkRead: (options: {
|
||||||
|
|
|
@ -5630,7 +5630,7 @@ async function removeAllProfileKeyCredentials(): Promise<void> {
|
||||||
async function saveEditedMessage(
|
async function saveEditedMessage(
|
||||||
mainMessage: MessageType,
|
mainMessage: MessageType,
|
||||||
ourUuid: UUIDStringType,
|
ourUuid: UUIDStringType,
|
||||||
{ fromId, messageId, readStatus, sentAt }: EditedMessageType
|
{ conversationId, messageId, readStatus, sentAt }: EditedMessageType
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const db = getInstance();
|
const db = getInstance();
|
||||||
|
|
||||||
|
@ -5644,12 +5644,12 @@ async function saveEditedMessage(
|
||||||
|
|
||||||
const [query, params] = sql`
|
const [query, params] = sql`
|
||||||
INSERT INTO edited_messages (
|
INSERT INTO edited_messages (
|
||||||
fromId,
|
conversationId,
|
||||||
messageId,
|
messageId,
|
||||||
sentAt,
|
sentAt,
|
||||||
readStatus
|
readStatus
|
||||||
) VALUES (
|
) VALUES (
|
||||||
${fromId},
|
${conversationId},
|
||||||
${messageId},
|
${messageId},
|
||||||
${sentAt},
|
${sentAt},
|
||||||
${readStatus}
|
${readStatus}
|
||||||
|
@ -5675,10 +5675,10 @@ async function _getAllEditedMessages(): Promise<
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getUnreadEditedMessagesAndMarkRead({
|
async function getUnreadEditedMessagesAndMarkRead({
|
||||||
fromId,
|
conversationId,
|
||||||
newestUnreadAt,
|
newestUnreadAt,
|
||||||
}: {
|
}: {
|
||||||
fromId: string;
|
conversationId: string;
|
||||||
newestUnreadAt: number;
|
newestUnreadAt: number;
|
||||||
}): Promise<GetUnreadByConversationAndMarkReadResultType> {
|
}): Promise<GetUnreadByConversationAndMarkReadResultType> {
|
||||||
const db = getInstance();
|
const db = getInstance();
|
||||||
|
@ -5695,7 +5695,7 @@ async function getUnreadEditedMessagesAndMarkRead({
|
||||||
ON messages.id = edited_messages.messageId
|
ON messages.id = edited_messages.messageId
|
||||||
WHERE
|
WHERE
|
||||||
edited_messages.readStatus = ${ReadStatus.Unread} AND
|
edited_messages.readStatus = ${ReadStatus.Unread} AND
|
||||||
edited_messages.fromId = ${fromId} AND
|
edited_messages.conversationId = ${conversationId} AND
|
||||||
received_at <= ${newestUnreadAt}
|
received_at <= ${newestUnreadAt}
|
||||||
ORDER BY messages.received_at DESC, messages.sent_at DESC;
|
ORDER BY messages.received_at DESC, messages.sent_at DESC;
|
||||||
`;
|
`;
|
||||||
|
@ -5711,7 +5711,7 @@ async function getUnreadEditedMessagesAndMarkRead({
|
||||||
readStatus = ${ReadStatus.Read}
|
readStatus = ${ReadStatus.Read}
|
||||||
WHERE
|
WHERE
|
||||||
readStatus = ${ReadStatus.Unread} AND
|
readStatus = ${ReadStatus.Unread} AND
|
||||||
fromId = ${fromId} AND
|
conversationId = ${conversationId} AND
|
||||||
sentAt <= ${newestSentAt};
|
sentAt <= ${newestSentAt};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
29
ts/sql/migrations/82-edited-messages-read-index.ts
Normal file
29
ts/sql/migrations/82-edited-messages-read-index.ts
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
// Copyright 2023 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import type { Database } from '@signalapp/better-sqlite3';
|
||||||
|
|
||||||
|
import type { LoggerType } from '../../types/Logging';
|
||||||
|
|
||||||
|
export default function updateToSchemaVersion82(
|
||||||
|
currentVersion: number,
|
||||||
|
db: Database,
|
||||||
|
logger: LoggerType
|
||||||
|
): void {
|
||||||
|
if (currentVersion >= 82) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
db.transaction(() => {
|
||||||
|
db.exec(`
|
||||||
|
ALTER TABLE edited_messages DROP COLUMN fromId;
|
||||||
|
ALTER TABLE edited_messages ADD COLUMN conversationId STRING;
|
||||||
|
|
||||||
|
CREATE INDEX edited_messages_unread ON edited_messages (readStatus, conversationId);
|
||||||
|
`);
|
||||||
|
|
||||||
|
db.pragma('user_version = 82');
|
||||||
|
})();
|
||||||
|
|
||||||
|
logger.info('updateToSchemaVersion82: success!');
|
||||||
|
}
|
|
@ -57,6 +57,7 @@ import updateToSchemaVersion78 from './78-merge-receipt-jobs';
|
||||||
import updateToSchemaVersion79 from './79-paging-lightbox';
|
import updateToSchemaVersion79 from './79-paging-lightbox';
|
||||||
import updateToSchemaVersion80 from './80-edited-messages';
|
import updateToSchemaVersion80 from './80-edited-messages';
|
||||||
import updateToSchemaVersion81 from './81-contact-removed-notification';
|
import updateToSchemaVersion81 from './81-contact-removed-notification';
|
||||||
|
import updateToSchemaVersion82 from './82-edited-messages-read-index';
|
||||||
|
|
||||||
function updateToSchemaVersion1(
|
function updateToSchemaVersion1(
|
||||||
currentVersion: number,
|
currentVersion: number,
|
||||||
|
@ -1984,6 +1985,7 @@ export const SCHEMA_VERSIONS = [
|
||||||
|
|
||||||
updateToSchemaVersion80,
|
updateToSchemaVersion80,
|
||||||
updateToSchemaVersion81,
|
updateToSchemaVersion81,
|
||||||
|
updateToSchemaVersion82,
|
||||||
];
|
];
|
||||||
|
|
||||||
export function updateSchema(db: Database, logger: LoggerType): void {
|
export function updateSchema(db: Database, logger: LoggerType): void {
|
||||||
|
|
|
@ -138,6 +138,7 @@ import { DAY } from '../../util/durations';
|
||||||
import { isNotNil } from '../../util/isNotNil';
|
import { isNotNil } from '../../util/isNotNil';
|
||||||
import { PanelType } from '../../types/Panels';
|
import { PanelType } from '../../types/Panels';
|
||||||
import { startConversation } from '../../util/startConversation';
|
import { startConversation } from '../../util/startConversation';
|
||||||
|
import { getMessageSentTimestamp } from '../../util/getMessageSentTimestamp';
|
||||||
import { UUIDKind } from '../../types/UUID';
|
import { UUIDKind } from '../../types/UUID';
|
||||||
import { removeLinkPreview } from '../../services/LinkPreview';
|
import { removeLinkPreview } from '../../services/LinkPreview';
|
||||||
import type {
|
import type {
|
||||||
|
@ -1822,7 +1823,7 @@ export const markViewed = (messageId: string): void => {
|
||||||
|
|
||||||
const senderE164 = message.get('source');
|
const senderE164 = message.get('source');
|
||||||
const senderUuid = message.get('sourceUuid');
|
const senderUuid = message.get('sourceUuid');
|
||||||
const timestamp = message.get('sent_at');
|
const timestamp = getMessageSentTimestamp(message.attributes, { log });
|
||||||
|
|
||||||
message.set(messageUpdaterMarkViewed(message.attributes, Date.now()));
|
message.set(messageUpdaterMarkViewed(message.attributes, Date.now()));
|
||||||
|
|
||||||
|
@ -2985,7 +2986,7 @@ function deleteMessagesForEveryone(
|
||||||
|
|
||||||
await sendDeleteForEveryoneMessage(conversation.attributes, {
|
await sendDeleteForEveryoneMessage(conversation.attributes, {
|
||||||
id: message.id,
|
id: message.id,
|
||||||
timestamp: message.get('sent_at'),
|
timestamp: getMessageSentTimestamp(message.attributes, { log }),
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
hasError = true;
|
hasError = true;
|
||||||
|
|
42
ts/util/getMessageSentTimestamp.ts
Normal file
42
ts/util/getMessageSentTimestamp.ts
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
// Copyright 2023 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import type { MessageAttributesType } from '../model-types.d';
|
||||||
|
import type { LoggerType } from '../types/Logging';
|
||||||
|
import { assertDev } from './assert';
|
||||||
|
|
||||||
|
export type GetMessageSentTimestampOptionsType = Readonly<{
|
||||||
|
includeEdits?: boolean;
|
||||||
|
log: LoggerType;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export function getMessageSentTimestamp(
|
||||||
|
{
|
||||||
|
editMessageTimestamp,
|
||||||
|
sent_at: sentAt,
|
||||||
|
timestamp,
|
||||||
|
}: Pick<
|
||||||
|
MessageAttributesType,
|
||||||
|
'editMessageTimestamp' | 'sent_at' | 'timestamp'
|
||||||
|
>,
|
||||||
|
{ includeEdits = true, log }: GetMessageSentTimestampOptionsType
|
||||||
|
): number {
|
||||||
|
if (includeEdits && editMessageTimestamp) {
|
||||||
|
return editMessageTimestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sentAt) {
|
||||||
|
return sentAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timestamp) {
|
||||||
|
log.error('message lacked sent_at. Falling back to timestamp');
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
assertDev(
|
||||||
|
false,
|
||||||
|
'message lacked sent_at and timestamp. Falling back to current time'
|
||||||
|
);
|
||||||
|
return Date.now();
|
||||||
|
}
|
17
ts/util/getMessageSentTimestampSet.ts
Normal file
17
ts/util/getMessageSentTimestampSet.ts
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
// Copyright 2023 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import type { MessageAttributesType } from '../model-types.d';
|
||||||
|
|
||||||
|
export function getMessageSentTimestampSet({
|
||||||
|
sent_at: sentAt,
|
||||||
|
editHistory,
|
||||||
|
}: Pick<
|
||||||
|
MessageAttributesType,
|
||||||
|
'sent_at' | 'editHistory'
|
||||||
|
>): ReadonlySet<number> {
|
||||||
|
return new Set([
|
||||||
|
sentAt,
|
||||||
|
...(editHistory?.map(({ timestamp }) => timestamp) ?? []),
|
||||||
|
]);
|
||||||
|
}
|
|
@ -240,7 +240,7 @@ export async function handleEditMessage(
|
||||||
window.Whisper.deliveryReceiptQueue.add(() => {
|
window.Whisper.deliveryReceiptQueue.add(() => {
|
||||||
window.Whisper.deliveryReceiptBatcher.add({
|
window.Whisper.deliveryReceiptBatcher.add({
|
||||||
messageId: mainMessage.id,
|
messageId: mainMessage.id,
|
||||||
conversationId: editAttributes.fromId,
|
conversationId: editAttributes.conversationId,
|
||||||
senderE164: editAttributes.message.source,
|
senderE164: editAttributes.message.source,
|
||||||
senderUuid: editAttributes.message.sourceUuid,
|
senderUuid: editAttributes.message.sourceUuid,
|
||||||
timestamp: editAttributes.message.timestamp,
|
timestamp: editAttributes.message.timestamp,
|
||||||
|
@ -263,7 +263,7 @@ export async function handleEditMessage(
|
||||||
mainMessageModel.attributes,
|
mainMessageModel.attributes,
|
||||||
window.textsecure.storage.user.getCheckedUuid().toString(),
|
window.textsecure.storage.user.getCheckedUuid().toString(),
|
||||||
{
|
{
|
||||||
fromId: editAttributes.fromId,
|
conversationId: editAttributes.conversationId,
|
||||||
messageId: mainMessage.id,
|
messageId: mainMessage.id,
|
||||||
readStatus,
|
readStatus,
|
||||||
sentAt: upgradedEditedMessageData.timestamp,
|
sentAt: upgradedEditedMessageData.timestamp,
|
||||||
|
|
|
@ -14,8 +14,10 @@ import { getContact } from '../messages/helpers';
|
||||||
import { getQuoteBodyText } from './getQuoteBodyText';
|
import { getQuoteBodyText } from './getQuoteBodyText';
|
||||||
import { isGIF } from '../types/Attachment';
|
import { isGIF } from '../types/Attachment';
|
||||||
import { isGiftBadge, isTapToView } from '../state/selectors/message';
|
import { isGiftBadge, isTapToView } from '../state/selectors/message';
|
||||||
|
import * as log from '../logging/log';
|
||||||
import { map, take, collect } from './iterables';
|
import { map, take, collect } from './iterables';
|
||||||
import { strictAssert } from './assert';
|
import { strictAssert } from './assert';
|
||||||
|
import { getMessageSentTimestamp } from './getMessageSentTimestamp';
|
||||||
|
|
||||||
export async function makeQuote(
|
export async function makeQuote(
|
||||||
quotedMessage: MessageAttributesType
|
quotedMessage: MessageAttributesType
|
||||||
|
@ -27,14 +29,13 @@ export async function makeQuote(
|
||||||
const {
|
const {
|
||||||
attachments,
|
attachments,
|
||||||
bodyRanges,
|
bodyRanges,
|
||||||
editMessageTimestamp,
|
|
||||||
id: messageId,
|
id: messageId,
|
||||||
payment,
|
payment,
|
||||||
preview,
|
preview,
|
||||||
sticker,
|
sticker,
|
||||||
} = quotedMessage;
|
} = quotedMessage;
|
||||||
|
|
||||||
const quoteId = editMessageTimestamp || quotedMessage.sent_at;
|
const quoteId = getMessageSentTimestamp(quotedMessage, { log });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
authorUuid: contact.get('uuid'),
|
authorUuid: contact.get('uuid'),
|
||||||
|
|
|
@ -45,7 +45,7 @@ export async function markConversationRead(
|
||||||
includeStoryReplies: !isGroup(conversationAttrs),
|
includeStoryReplies: !isGroup(conversationAttrs),
|
||||||
}),
|
}),
|
||||||
window.Signal.Data.getUnreadEditedMessagesAndMarkRead({
|
window.Signal.Data.getUnreadEditedMessagesAndMarkRead({
|
||||||
fromId: conversationId,
|
conversationId,
|
||||||
newestUnreadAt,
|
newestUnreadAt,
|
||||||
}),
|
}),
|
||||||
window.Signal.Data.getUnreadReactionsAndMarkRead({
|
window.Signal.Data.getUnreadReactionsAndMarkRead({
|
||||||
|
|
|
@ -30,6 +30,7 @@ import { isSignalConversation } from './isSignalConversation';
|
||||||
import { strictAssert } from './assert';
|
import { strictAssert } from './assert';
|
||||||
import { timeAndLogIfTooLong } from './timeAndLogIfTooLong';
|
import { timeAndLogIfTooLong } from './timeAndLogIfTooLong';
|
||||||
import { makeQuote } from './makeQuote';
|
import { makeQuote } from './makeQuote';
|
||||||
|
import { getMessageSentTimestamp } from './getMessageSentTimestamp';
|
||||||
|
|
||||||
const SEND_REPORT_THRESHOLD_MS = 25;
|
const SEND_REPORT_THRESHOLD_MS = 25;
|
||||||
|
|
||||||
|
@ -82,9 +83,12 @@ export async function sendEditedMessage(
|
||||||
}
|
}
|
||||||
|
|
||||||
const timestamp = Date.now();
|
const timestamp = Date.now();
|
||||||
const targetSentTimestamp =
|
const targetSentTimestamp = getMessageSentTimestamp(
|
||||||
targetMessage.attributes.editMessageTimestamp ??
|
targetMessage.attributes,
|
||||||
targetMessage.attributes.timestamp;
|
{
|
||||||
|
log,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
log.info(`${idLog}: edited(${timestamp}) original(${targetSentTimestamp})`);
|
log.info(`${idLog}: edited(${timestamp}) original(${targetSentTimestamp})`);
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue