Remove refs to MessageModel in conversations.ts

This commit is contained in:
Fedor Indutny 2024-07-25 16:29:49 -07:00 committed by GitHub
parent 2550af9c91
commit cc6ff0b554
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 312 additions and 357 deletions

View file

@ -2025,13 +2025,12 @@ export async function createGroupV2(
forceSave: true, forceSave: true,
ourAci, ourAci,
}); });
let model = new window.Whisper.Message(createdTheGroupMessage); window.MessageCache.__DEPRECATED$register(
model = window.MessageCache.__DEPRECATED$register( createdTheGroupMessage.id,
model.id, new window.Whisper.Message(createdTheGroupMessage),
model,
'createGroupV2' 'createGroupV2'
); );
conversation.trigger('newmessage', model); conversation.trigger('newmessage', createdTheGroupMessage);
if (expireTimer) { if (expireTimer) {
await conversation.updateExpirationTimer(expireTimer, { await conversation.updateExpirationTimer(expireTimer, {
@ -3435,13 +3434,12 @@ async function appendChangeMessages(
continue; continue;
} }
let model = new window.Whisper.Message(changeMessage); window.MessageCache.__DEPRECATED$register(
model = window.MessageCache.__DEPRECATED$register( changeMessage.id,
model.id, new window.Whisper.Message(changeMessage),
model,
'appendChangeMessages' 'appendChangeMessages'
); );
conversation.trigger('newmessage', model); conversation.trigger('newmessage', changeMessage);
newMessages += 1; newMessages += 1;
} }

View file

@ -341,13 +341,12 @@ export async function sendReaction(
forceSave: true, forceSave: true,
}); });
void conversation.addSingleMessage(
window.MessageCache.__DEPRECATED$register( window.MessageCache.__DEPRECATED$register(
reactionMessage.id, reactionMessage.id,
reactionMessage, reactionMessage,
'sendReaction' 'sendReaction'
)
); );
void conversation.addSingleMessage(reactionMessage.attributes);
} }
} }

View file

@ -4,8 +4,10 @@
import { z } from 'zod'; import { z } from 'zod';
import { groupBy } from 'lodash'; import { groupBy } from 'lodash';
import type { MessageModel } from '../models/messages'; import type {
import type { MessageAttributesType } from '../model-types.d'; MessageAttributesType,
ReadonlyMessageAttributesType,
} from '../model-types.d';
import type { SendStateByConversationId } from '../messages/MessageSendState'; import type { SendStateByConversationId } from '../messages/MessageSendState';
import { isOutgoing, isStory } from '../state/selectors/message'; import { isOutgoing, isStory } from '../state/selectors/message';
import { getOwn } from '../util/getOwn'; import { getOwn } from '../util/getOwn';
@ -376,7 +378,7 @@ const wasDeliveredWithSealedSender = (
const shouldDropReceipt = ( const shouldDropReceipt = (
receipt: MessageReceiptAttributesType, receipt: MessageReceiptAttributesType,
message: MessageAttributesType message: ReadonlyMessageAttributesType
): boolean => { ): boolean => {
const { type } = receipt.receiptSync; const { type } = receipt.receiptSync;
switch (type) { switch (type) {
@ -395,25 +397,25 @@ const shouldDropReceipt = (
}; };
export async function forMessage( export async function forMessage(
message: MessageModel message: ReadonlyMessageAttributesType
): Promise<Array<MessageReceiptAttributesType>> { ): Promise<Array<MessageReceiptAttributesType>> {
if (!isOutgoing(message.attributes) && !isStory(message.attributes)) { if (!isOutgoing(message) && !isStory(message)) {
return []; return [];
} }
const logId = `MessageReceipts.forMessage(${getMessageIdForLogging( const logId = `MessageReceipts.forMessage(${getMessageIdForLogging(
message.attributes message
)})`; )})`;
const ourAci = window.textsecure.storage.user.getCheckedAci(); const ourAci = window.textsecure.storage.user.getCheckedAci();
const sourceServiceId = getSourceServiceId(message.attributes); const sourceServiceId = getSourceServiceId(message);
if (ourAci !== sourceServiceId) { if (ourAci !== sourceServiceId) {
return []; return [];
} }
const receiptValues = Array.from(cachedReceipts.values()); const receiptValues = Array.from(cachedReceipts.values());
const sentAt = getMessageSentTimestamp(message.attributes, { log }); const sentAt = getMessageSentTimestamp(message, { log });
const result = receiptValues.filter( const result = receiptValues.filter(
item => item.receiptSync.messageSentAt === sentAt item => item.receiptSync.messageSentAt === sentAt
); );
@ -427,7 +429,7 @@ export async function forMessage(
} }
return result.filter(receipt => { return result.filter(receipt => {
if (shouldDropReceipt(receipt, message.attributes)) { if (shouldDropReceipt(receipt, message)) {
log.info( log.info(
`${logId}: Dropping an early receipt ${receipt.receiptSync.type} for message ${sentAt}` `${logId}: Dropping an early receipt ${receipt.receiptSync.type} for message ${sentAt}`
); );

View file

@ -2,7 +2,10 @@
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import type { AciString } from '../types/ServiceId'; import type { AciString } from '../types/ServiceId';
import type { MessageAttributesType } from '../model-types.d'; import type {
MessageAttributesType,
ReadonlyMessageAttributesType,
} from '../model-types.d';
import type { MessageModel } from '../models/messages'; import type { MessageModel } from '../models/messages';
import type { ReactionSource } from '../reactions/ReactionSource'; import type { ReactionSource } from '../reactions/ReactionSource';
import { DataReader } from '../sql/Client'; import { DataReader } from '../sql/Client';
@ -41,11 +44,11 @@ function remove(reaction: ReactionAttributesType): void {
} }
export function findReactionsForMessage( export function findReactionsForMessage(
message: MessageModel message: ReadonlyMessageAttributesType
): Array<ReactionAttributesType> { ): Array<ReactionAttributesType> {
const matchingReactions = Array.from(reactions.values()).filter(reaction => { const matchingReactions = Array.from(reactions.values()).filter(reaction => {
return isMessageAMatchForReaction({ return isMessageAMatchForReaction({
message: message.attributes, message,
targetTimestamp: reaction.targetTimestamp, targetTimestamp: reaction.targetTimestamp,
targetAuthorAci: reaction.targetAuthorAci, targetAuthorAci: reaction.targetAuthorAci,
reactionSenderConversationId: reaction.fromId, reactionSenderConversationId: reaction.fromId,
@ -99,7 +102,7 @@ function isMessageAMatchForReaction({
targetAuthorAci, targetAuthorAci,
reactionSenderConversationId, reactionSenderConversationId,
}: { }: {
message: MessageAttributesType; message: ReadonlyMessageAttributesType;
targetTimestamp: number; targetTimestamp: number;
targetAuthorAci: string; targetAuthorAci: string;
reactionSenderConversationId: string; reactionSenderConversationId: string;

View file

@ -3,7 +3,7 @@
import { z } from 'zod'; import { z } from 'zod';
import type { MessageModel } from '../models/messages'; import type { ReadonlyMessageAttributesType } from '../model-types.d';
import * as Errors from '../types/errors'; import * as Errors from '../types/errors';
import * as log from '../logging/log'; import * as log from '../logging/log';
import { StartupQueue } from '../util/StartupQueue'; import { StartupQueue } from '../util/StartupQueue';
@ -88,18 +88,16 @@ async function maybeItIsAReactionReadSync(
} }
export async function forMessage( export async function forMessage(
message: MessageModel message: ReadonlyMessageAttributesType
): Promise<ReadSyncAttributesType | null> { ): Promise<ReadSyncAttributesType | null> {
const logId = `ReadSyncs.forMessage(${getMessageIdForLogging( const logId = `ReadSyncs.forMessage(${getMessageIdForLogging(message)})`;
message.attributes
)})`;
const sender = window.ConversationController.lookupOrCreate({ const sender = window.ConversationController.lookupOrCreate({
e164: message.get('source'), e164: message.source,
serviceId: message.get('sourceServiceId'), serviceId: message.sourceServiceId,
reason: logId, reason: logId,
}); });
const messageTimestamp = getMessageSentTimestamp(message.attributes, { const messageTimestamp = getMessageSentTimestamp(message, {
log, log,
}); });
const readSyncValues = Array.from(readSyncs.values()); const readSyncValues = Array.from(readSyncs.values());
@ -169,7 +167,9 @@ export async function onSync(sync: ReadSyncAttributesType): Promise<void> {
// onReadMessage may result in messages older than this one being // onReadMessage may result in messages older than this one being
// marked read. We want those messages to have the same expire timer // marked read. We want those messages to have the same expire timer
// start time as this one, so we pass the readAt value through. // start time as this one, so we pass the readAt value through.
drop(conversation.onReadMessage(message, readAt, newestSentAt)); drop(
conversation.onReadMessage(message.attributes, readAt, newestSentAt)
);
}; };
// only available during initialization // only available during initialization

View file

@ -2,7 +2,7 @@
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import type { AciString } from '../types/ServiceId'; import type { AciString } from '../types/ServiceId';
import type { MessageModel } from '../models/messages'; import type { ReadonlyMessageAttributesType } from '../model-types.d';
import { DataReader } from '../sql/Client'; import { DataReader } from '../sql/Client';
import * as Errors from '../types/errors'; import * as Errors from '../types/errors';
import * as log from '../logging/log'; import * as log from '../logging/log';
@ -23,18 +23,18 @@ function remove(sync: ViewOnceOpenSyncAttributesType): void {
} }
export function forMessage( export function forMessage(
message: MessageModel message: ReadonlyMessageAttributesType
): ViewOnceOpenSyncAttributesType | null { ): ViewOnceOpenSyncAttributesType | null {
const logId = `ViewOnceOpenSyncs.forMessage(${getMessageIdForLogging( const logId = `ViewOnceOpenSyncs.forMessage(${getMessageIdForLogging(
message.attributes message
)})`; )})`;
const viewOnceSyncValues = Array.from(viewOnceSyncs.values()); const viewOnceSyncValues = Array.from(viewOnceSyncs.values());
const syncBySourceServiceId = viewOnceSyncValues.find(item => { const syncBySourceServiceId = viewOnceSyncValues.find(item => {
return ( return (
item.sourceAci === message.get('sourceServiceId') && item.sourceAci === message.sourceServiceId &&
item.timestamp === message.get('sent_at') item.timestamp === message.sent_at
); );
}); });
@ -45,10 +45,7 @@ export function forMessage(
} }
const syncBySource = viewOnceSyncValues.find(item => { const syncBySource = viewOnceSyncValues.find(item => {
return ( return item.source === message.source && item.timestamp === message.sent_at;
item.source === message.get('source') &&
item.timestamp === message.get('sent_at')
);
}); });
if (syncBySource) { if (syncBySource) {
log.info(`${logId}: Found early view once open sync for message`); log.info(`${logId}: Found early view once open sync for message`);

View file

@ -3,7 +3,7 @@
import { z } from 'zod'; import { z } from 'zod';
import type { MessageModel } from '../models/messages'; import type { ReadonlyMessageAttributesType } from '../model-types.d';
import * as Errors from '../types/errors'; import * as Errors from '../types/errors';
import * as log from '../logging/log'; import * as log from '../logging/log';
import { GiftBadgeStates } from '../components/conversation/Message'; import { GiftBadgeStates } from '../components/conversation/Message';
@ -44,18 +44,16 @@ async function remove(sync: ViewSyncAttributesType): Promise<void> {
} }
export async function forMessage( export async function forMessage(
message: MessageModel message: ReadonlyMessageAttributesType
): Promise<Array<ViewSyncAttributesType>> { ): Promise<Array<ViewSyncAttributesType>> {
const logId = `ViewSyncs.forMessage(${getMessageIdForLogging( const logId = `ViewSyncs.forMessage(${getMessageIdForLogging(message)})`;
message.attributes
)})`;
const sender = window.ConversationController.lookupOrCreate({ const sender = window.ConversationController.lookupOrCreate({
e164: message.get('source'), e164: message.source,
serviceId: message.get('sourceServiceId'), serviceId: message.sourceServiceId,
reason: logId, reason: logId,
}); });
const messageTimestamp = getMessageSentTimestamp(message.attributes, { const messageTimestamp = getMessageSentTimestamp(message, {
log, log,
}); });

View file

@ -22,6 +22,9 @@ import { isShallowEqual } from '../util/isShallowEqual';
import { getInitials } from '../util/getInitials'; import { getInitials } from '../util/getInitials';
import { clearTimeoutIfNecessary } from '../util/clearTimeoutIfNecessary'; import { clearTimeoutIfNecessary } from '../util/clearTimeoutIfNecessary';
import { getMessageSentTimestamp } from '../util/getMessageSentTimestamp'; import { getMessageSentTimestamp } from '../util/getMessageSentTimestamp';
import { getNotificationTextForMessage } from '../util/getNotificationTextForMessage';
import { getNotificationDataForMessage } from '../util/getNotificationDataForMessage';
import type { ProfileNameChangeType } from '../util/getStringForProfileChange';
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 { areWeAdmin } from '../util/areWeAdmin'; import { areWeAdmin } from '../util/areWeAdmin';
@ -35,6 +38,7 @@ import {
import { getDraftPreview } from '../util/getDraftPreview'; import { getDraftPreview } from '../util/getDraftPreview';
import { hasDraft } from '../util/hasDraft'; import { hasDraft } from '../util/hasDraft';
import { missingCaseError } from '../util/missingCaseError'; import { missingCaseError } from '../util/missingCaseError';
import { hydrateStoryContext } from '../util/hydrateStoryContext';
import * as Conversation from '../types/Conversation'; import * as Conversation from '../types/Conversation';
import type { StickerType, StickerWithHydratedData } from '../types/Stickers'; import type { StickerType, StickerWithHydratedData } from '../types/Stickers';
import * as Stickers from '../types/Stickers'; import * as Stickers from '../types/Stickers';
@ -56,7 +60,6 @@ import type {
ConversationColorType, ConversationColorType,
CustomColorType, CustomColorType,
} from '../types/Colors'; } from '../types/Colors';
import type { MessageModel } from './messages';
import { getAuthor } from '../messages/helpers'; import { getAuthor } from '../messages/helpers';
import { strictAssert } from '../util/assert'; import { strictAssert } from '../util/assert';
import { isConversationMuted } from '../util/isConversationMuted'; import { isConversationMuted } from '../util/isConversationMuted';
@ -173,6 +176,7 @@ import OS from '../util/os/osMain';
import { getMessageAuthorText } from '../util/getMessageAuthorText'; import { getMessageAuthorText } from '../util/getMessageAuthorText';
import { downscaleOutgoingAttachment } from '../util/attachments'; import { downscaleOutgoingAttachment } from '../util/attachments';
import { MessageRequestResponseEvent } from '../types/MessageRequestResponseEvent'; import { MessageRequestResponseEvent } from '../types/MessageRequestResponseEvent';
import { hasExpiration } from '../types/Message2';
import type { MessageToDelete } from '../textsecure/messageReceiverEvents'; import type { MessageToDelete } from '../textsecure/messageReceiverEvents';
import { import {
getConversationToDelete, getConversationToDelete,
@ -399,7 +403,7 @@ export class ConversationModel extends window.Backbone
// result in refresh via a getProps() call. See format() below. // result in refresh via a getProps() call. See format() below.
this.on( this.on(
'change', 'change',
(_model: MessageModel, options: { force?: boolean } = {}) => { (_model: ConversationModel, options: { force?: boolean } = {}) => {
const changedKeys = Object.keys(this.changed || {}); const changedKeys = Object.keys(this.changed || {});
const isPropsCacheStillValid = const isPropsCacheStillValid =
!options.force && !options.force &&
@ -1395,10 +1399,8 @@ export class ConversationModel extends window.Backbone
}); });
} }
async onNewMessage(message: MessageModel): Promise<void> { async onNewMessage(message: MessageAttributesType): Promise<void> {
const serviceId = message.get('sourceServiceId'); const { sourceServiceId: serviceId, source: e164, sourceDevice } = message;
const e164 = message.get('source');
const sourceDevice = message.get('sourceDevice');
const source = window.ConversationController.lookupOrCreate({ const source = window.ConversationController.lookupOrCreate({
serviceId, serviceId,
@ -1414,16 +1416,15 @@ export class ConversationModel extends window.Backbone
// If it's a group story reply or a story message, we don't want to update // If it's a group story reply or a story message, we don't want to update
// the last message or add new messages to redux. // the last message or add new messages to redux.
const isGroupStoryReply = const isGroupStoryReply = isGroup(this.attributes) && message.storyId;
isGroup(this.attributes) && message.get('storyId'); if (isGroupStoryReply || isStory(message)) {
if (isGroupStoryReply || isStory(message.attributes)) {
return; return;
} }
// Change to message request state if contact was removed and sent message. // Change to message request state if contact was removed and sent message.
if ( if (
this.get('removalStage') === 'justNotification' && this.get('removalStage') === 'justNotification' &&
isIncoming(message.attributes) isIncoming(message)
) { ) {
this.set({ this.set({
removalStage: 'messageRequest', removalStage: 'messageRequest',
@ -1438,7 +1439,7 @@ export class ConversationModel extends window.Backbone
// New messages might arrive while we're in the middle of a bulk fetch from the // New messages might arrive while we're in the middle of a bulk fetch from the
// database. We'll wait until that is done before moving forward. // database. We'll wait until that is done before moving forward.
async addSingleMessage( async addSingleMessage(
message: MessageModel, message: MessageAttributesType,
{ isJustSent }: { isJustSent: boolean } = { isJustSent: false } { isJustSent }: { isJustSent: boolean } = { isJustSent: false }
): Promise<void> { ): Promise<void> {
await this.beforeAddSingleMessage(message); await this.beforeAddSingleMessage(message);
@ -1446,8 +1447,10 @@ export class ConversationModel extends window.Backbone
this.debouncedUpdateLastMessage(); this.debouncedUpdateLastMessage();
} }
private async beforeAddSingleMessage(message: MessageModel): Promise<void> { private async beforeAddSingleMessage(
await message.hydrateStoryContext(undefined, { shouldSave: true }); message: MessageAttributesType
): Promise<void> {
await hydrateStoryContext(message.id, undefined, { shouldSave: true });
if (!this.newMessageQueue) { if (!this.newMessageQueue) {
this.newMessageQueue = new PQueue({ this.newMessageQueue = new PQueue({
@ -1463,7 +1466,7 @@ export class ConversationModel extends window.Backbone
} }
private doAddSingleMessage( private doAddSingleMessage(
message: MessageModel, message: MessageAttributesType,
{ isJustSent }: { isJustSent: boolean } { isJustSent }: { isJustSent: boolean }
): void { ): void {
const { messagesAdded } = window.reduxActions.conversations; const { messagesAdded } = window.reduxActions.conversations;
@ -1486,12 +1489,12 @@ export class ConversationModel extends window.Backbone
} else if ( } else if (
// The message has to be not a story or has to be a story reply in direct // The message has to be not a story or has to be a story reply in direct
// conversation. // conversation.
!isStory(message.attributes) && !isStory(message) &&
(message.get('storyId') == null || isDirectConversation(this.attributes)) (message.storyId == null || isDirectConversation(this.attributes))
) { ) {
messagesAdded({ messagesAdded({
conversationId, conversationId,
messages: [{ ...message.attributes }], messages: [{ ...message }],
isActive: window.SignalContext.activeWindowService.isActive(), isActive: window.SignalContext.activeWindowService.isActive(),
isJustSent, isJustSent,
isNewMessage: true, isNewMessage: true,
@ -1598,13 +1601,13 @@ export class ConversationModel extends window.Backbone
storyId: undefined, storyId: undefined,
}); });
const cleaned: Array<MessageModel> = await this.cleanModels(messages); const cleaned = await this.cleanAttributes(messages);
const scrollToMessageId = const scrollToMessageId =
setFocus && metrics.newest ? metrics.newest.id : undefined; setFocus && metrics.newest ? metrics.newest.id : undefined;
log.info( log.info(
`${logId}: loaded ${cleaned.length} messages, ` + `${logId}: loaded ${cleaned.length} messages, ` +
`latest timestamp=${cleaned.at(-1)?.get('sent_at')}` `latest timestamp=${cleaned.at(-1)?.sent_at}`
); );
// Because our `getOlderMessages` fetch above didn't specify a receivedAt, we got // Because our `getOlderMessages` fetch above didn't specify a receivedAt, we got
@ -1615,9 +1618,7 @@ export class ConversationModel extends window.Backbone
const unboundedFetch = true; const unboundedFetch = true;
messagesReset({ messagesReset({
conversationId, conversationId,
messages: cleaned.map((messageModel: MessageModel) => ({ messages: cleaned,
...messageModel.attributes,
})),
metrics, metrics,
scrollToMessageId, scrollToMessageId,
unboundedFetch, unboundedFetch,
@ -1666,18 +1667,16 @@ export class ConversationModel extends window.Backbone
return; return;
} }
const cleaned = await this.cleanModels(models); const cleaned = await this.cleanAttributes(models);
log.info( log.info(
`${logId}: loaded ${cleaned.length} messages, ` + `${logId}: loaded ${cleaned.length} messages, ` +
`first timestamp=${cleaned.at(0)?.get('sent_at')}` `first timestamp=${cleaned.at(0)?.sent_at}`
); );
messagesAdded({ messagesAdded({
conversationId, conversationId,
messages: cleaned.map((messageModel: MessageModel) => ({ messages: cleaned,
...messageModel.attributes,
})),
isActive: window.SignalContext.activeWindowService.isActive(), isActive: window.SignalContext.activeWindowService.isActive(),
isJustSent: false, isJustSent: false,
isNewMessage: false, isNewMessage: false,
@ -1726,12 +1725,10 @@ export class ConversationModel extends window.Backbone
return; return;
} }
const cleaned = await this.cleanModels(models); const cleaned = await this.cleanAttributes(models);
messagesAdded({ messagesAdded({
conversationId, conversationId,
messages: cleaned.map((messageModel: MessageModel) => ({ messages: cleaned,
...messageModel.attributes,
})),
isActive: window.SignalContext.activeWindowService.isActive(), isActive: window.SignalContext.activeWindowService.isActive(),
isJustSent: false, isJustSent: false,
isNewMessage: false, isNewMessage: false,
@ -1780,15 +1777,13 @@ export class ConversationModel extends window.Backbone
}); });
const all = [...older, message, ...newer]; const all = [...older, message, ...newer];
const cleaned: Array<MessageModel> = await this.cleanModels(all); const cleaned = await this.cleanAttributes(all);
const scrollToMessageId = const scrollToMessageId =
options && options.disableScroll ? undefined : messageId; options && options.disableScroll ? undefined : messageId;
messagesReset({ messagesReset({
conversationId, conversationId,
messages: cleaned.map((messageModel: MessageModel) => ({ messages: cleaned,
...messageModel.attributes,
})),
metrics, metrics,
scrollToMessageId, scrollToMessageId,
}); });
@ -1800,52 +1795,53 @@ export class ConversationModel extends window.Backbone
} }
} }
async cleanModels( async cleanAttributes(
messages: ReadonlyArray<MessageAttributesType> messages: ReadonlyArray<MessageAttributesType>
): Promise<Array<MessageModel>> { ): Promise<Array<MessageAttributesType>> {
const result = messages const present = messages.filter(message => Boolean(message.id));
.filter(message => Boolean(message.id))
.map(message =>
window.MessageCache.__DEPRECATED$register(
message.id,
message,
'cleanModels'
)
);
const eliminated = messages.length - result.length; const eliminated = messages.length - present.length;
if (eliminated > 0) { if (eliminated > 0) {
log.warn(`cleanModels: Eliminated ${eliminated} messages without an id`); log.warn(
`cleanAttributes: Eliminated ${eliminated} messages without an id`
);
} }
const ourAci = window.textsecure.storage.user.getCheckedAci(); const ourAci = window.textsecure.storage.user.getCheckedAci();
let upgraded = 0; let upgraded = 0;
for (let max = result.length, i = 0; i < max; i += 1) { const hydrated = await Promise.all(
const message = result[i]; present.map(async message => {
const { attributes } = message; const { schemaVersion } = message;
const { schemaVersion } = attributes;
const model = window.MessageCache.__DEPRECATED$register(
message.id,
message,
'cleanAttributes'
);
let upgradedMessage = message;
if ((schemaVersion || 0) < Message.VERSION_NEEDED_FOR_DISPLAY) { if ((schemaVersion || 0) < Message.VERSION_NEEDED_FOR_DISPLAY) {
// Yep, we really do want to wait for each of these // Yep, we really do want to wait for each of these
// eslint-disable-next-line no-await-in-loop upgradedMessage = await upgradeMessageSchema(message);
const upgradedMessage = await upgradeMessageSchema(attributes); model.set(upgradedMessage);
message.set(upgradedMessage);
// eslint-disable-next-line no-await-in-loop
await DataWriter.saveMessage(upgradedMessage, { ourAci }); await DataWriter.saveMessage(upgradedMessage, { ourAci });
upgraded += 1; upgraded += 1;
} }
}
if (upgraded > 0) {
log.warn(`cleanModels: Upgraded schema of ${upgraded} messages`);
}
await Promise.all( const patch = await hydrateStoryContext(message.id, undefined, {
result.map(model => shouldSave: true,
model.hydrateStoryContext(undefined, { shouldSave: true }) });
) if (patch) {
return { ...upgradedMessage, ...patch };
}
return upgradedMessage;
})
); );
if (upgraded > 0) {
log.warn(`cleanAttributes: Upgraded schema of ${upgraded} messages`);
}
return result; return hydrated;
} }
format(): ConversationType { format(): ConversationType {
@ -2178,16 +2174,12 @@ export class ConversationModel extends window.Backbone
messageRequestResponseEvent: event, messageRequestResponseEvent: event,
}; };
const id = await DataWriter.saveMessage(message, { await DataWriter.saveMessage(message, {
ourAci: window.textsecure.storage.user.getCheckedAci(), ourAci: window.textsecure.storage.user.getCheckedAci(),
forceSave: true, forceSave: true,
}); });
const model = new window.Whisper.Message({ window.MessageCache.toMessageAttributes(message);
...message, this.trigger('newmessage', message);
id,
});
window.MessageCache.toMessageAttributes(model.attributes);
this.trigger('newmessage', model);
drop(this.updateLastMessage()); drop(this.updateLastMessage());
} }
@ -2926,31 +2918,28 @@ export class ConversationModel extends window.Backbone
receivedAt, receivedAt,
}); });
const message = { const message: MessageAttributesType = {
id: generateGuid(),
conversationId: this.id, conversationId: this.id,
type: 'chat-session-refreshed', type: 'chat-session-refreshed',
timestamp: receivedAt,
sent_at: receivedAt, sent_at: receivedAt,
received_at: receivedAtCounter, received_at: receivedAtCounter,
received_at_ms: receivedAt, received_at_ms: receivedAt,
readStatus: ReadStatus.Unread, readStatus: ReadStatus.Unread,
seenStatus: SeenStatus.Unseen, seenStatus: SeenStatus.Unseen,
// TODO: DESKTOP-722 };
// this type does not fully implement the interface it is expected to
} as unknown as MessageAttributesType;
const id = await DataWriter.saveMessage(message, { await DataWriter.saveMessage(message, {
ourAci: window.textsecure.storage.user.getCheckedAci(), ourAci: window.textsecure.storage.user.getCheckedAci(),
}); });
const model = window.MessageCache.__DEPRECATED$register( window.MessageCache.__DEPRECATED$register(
id, message.id,
new window.Whisper.Message({ message,
...message,
id,
}),
'addChatSessionRefreshed' 'addChatSessionRefreshed'
); );
this.trigger('newmessage', model); this.trigger('newmessage', message);
void this.updateUnread(); void this.updateUnread();
} }
@ -2977,34 +2966,31 @@ export class ConversationModel extends window.Backbone
return; return;
} }
const message = { const message: MessageAttributesType = {
id: generateGuid(),
conversationId: this.id, conversationId: this.id,
type: 'delivery-issue', type: 'delivery-issue',
sourceServiceId: senderAci, sourceServiceId: senderAci,
sent_at: receivedAt, sent_at: receivedAt,
received_at: receivedAtCounter, received_at: receivedAtCounter,
received_at_ms: receivedAt, received_at_ms: receivedAt,
timestamp: receivedAt,
readStatus: ReadStatus.Unread, readStatus: ReadStatus.Unread,
seenStatus: SeenStatus.Unseen, seenStatus: SeenStatus.Unseen,
// TODO: DESKTOP-722 };
// this type does not fully implement the interface it is expected to
} as unknown as MessageAttributesType;
const id = await DataWriter.saveMessage(message, { await DataWriter.saveMessage(message, {
ourAci: window.textsecure.storage.user.getCheckedAci(), ourAci: window.textsecure.storage.user.getCheckedAci(),
}); });
const model = window.MessageCache.__DEPRECATED$register( window.MessageCache.__DEPRECATED$register(
id, message.id,
new window.Whisper.Message({ message,
...message,
id,
}),
'addDeliveryIssue' 'addDeliveryIssue'
); );
this.trigger('newmessage', model); this.trigger('newmessage', message);
await this.notify(model); await this.notify(message);
void this.updateUnread(); void this.updateUnread();
} }
@ -3048,13 +3034,13 @@ export class ConversationModel extends window.Backbone
ourAci: window.textsecure.storage.user.getCheckedAci(), ourAci: window.textsecure.storage.user.getCheckedAci(),
forceSave: true, forceSave: true,
}); });
const model = window.MessageCache.__DEPRECATED$register( window.MessageCache.__DEPRECATED$register(
message.id, message.id,
new window.Whisper.Message(message), message,
'addKeyChange' 'addKeyChange'
); );
this.trigger('newmessage', model); this.trigger('newmessage', message);
const serviceId = this.getServiceId(); const serviceId = this.getServiceId();
@ -3108,20 +3094,17 @@ export class ConversationModel extends window.Backbone
schemaVersion: Message.VERSION_NEEDED_FOR_DISPLAY, schemaVersion: Message.VERSION_NEEDED_FOR_DISPLAY,
}; };
const id = await DataWriter.saveMessage(message, { await DataWriter.saveMessage(message, {
ourAci: window.textsecure.storage.user.getCheckedAci(), ourAci: window.textsecure.storage.user.getCheckedAci(),
forceSave: true, forceSave: true,
}); });
const model = window.MessageCache.__DEPRECATED$register( window.MessageCache.__DEPRECATED$register(
id, message.id,
new window.Whisper.Message({ message,
...message,
id,
}),
'addConversationMerge' 'addConversationMerge'
); );
this.trigger('newmessage', model); this.trigger('newmessage', message);
} }
async addPhoneNumberDiscoveryIfNeeded(originalPni: PniString): Promise<void> { async addPhoneNumberDiscoveryIfNeeded(originalPni: PniString): Promise<void> {
@ -3160,20 +3143,17 @@ export class ConversationModel extends window.Backbone
schemaVersion: Message.VERSION_NEEDED_FOR_DISPLAY, schemaVersion: Message.VERSION_NEEDED_FOR_DISPLAY,
}; };
const id = await DataWriter.saveMessage(message, { await DataWriter.saveMessage(message, {
ourAci: window.textsecure.storage.user.getCheckedAci(), ourAci: window.textsecure.storage.user.getCheckedAci(),
forceSave: true, forceSave: true,
}); });
const model = window.MessageCache.__DEPRECATED$register( window.MessageCache.__DEPRECATED$register(
id, message.id,
new window.Whisper.Message({ message,
...message,
id,
}),
'addPhoneNumberDiscoveryIfNeeded' 'addPhoneNumberDiscoveryIfNeeded'
); );
this.trigger('newmessage', model); this.trigger('newmessage', message);
} }
async addVerifiedChange( async addVerifiedChange(
@ -3215,13 +3195,13 @@ export class ConversationModel extends window.Backbone
ourAci: window.textsecure.storage.user.getCheckedAci(), ourAci: window.textsecure.storage.user.getCheckedAci(),
forceSave: true, forceSave: true,
}); });
const model = window.MessageCache.__DEPRECATED$register( window.MessageCache.__DEPRECATED$register(
message.id, message.id,
new window.Whisper.Message(message), message,
'addVerifiedChange' 'addVerifiedChange'
); );
this.trigger('newmessage', model); this.trigger('newmessage', message);
drop(this.updateUnread()); drop(this.updateUnread());
const serviceId = this.getServiceId(); const serviceId = this.getServiceId();
@ -3237,11 +3217,12 @@ export class ConversationModel extends window.Backbone
} }
async addProfileChange( async addProfileChange(
profileChange: unknown, profileChange: ProfileNameChangeType,
conversationId?: string conversationId?: string
): Promise<void> { ): Promise<void> {
const now = Date.now(); const now = Date.now();
const message = { const message: MessageAttributesType = {
id: generateGuid(),
conversationId: this.id, conversationId: this.id,
type: 'profile-change', type: 'profile-change',
sent_at: now, sent_at: now,
@ -3249,24 +3230,21 @@ export class ConversationModel extends window.Backbone
received_at_ms: now, received_at_ms: now,
readStatus: ReadStatus.Read, readStatus: ReadStatus.Read,
seenStatus: SeenStatus.NotApplicable, seenStatus: SeenStatus.NotApplicable,
timestamp: now,
changedId: conversationId || this.id, changedId: conversationId || this.id,
profileChange, profileChange,
// TODO: DESKTOP-722 };
} as unknown as MessageAttributesType;
const id = await DataWriter.saveMessage(message, { await DataWriter.saveMessage(message, {
ourAci: window.textsecure.storage.user.getCheckedAci(), ourAci: window.textsecure.storage.user.getCheckedAci(),
}); });
const model = window.MessageCache.__DEPRECATED$register( window.MessageCache.__DEPRECATED$register(
id, message.id,
new window.Whisper.Message({ message,
...message,
id,
}),
'addProfileChange' 'addProfileChange'
); );
this.trigger('newmessage', model); this.trigger('newmessage', message);
const serviceId = this.getServiceId(); const serviceId = this.getServiceId();
if (isDirectConversation(this.attributes) && serviceId) { if (isDirectConversation(this.attributes) && serviceId) {
@ -3287,37 +3265,33 @@ export class ConversationModel extends window.Backbone
extra: Partial<MessageAttributesType> = {} extra: Partial<MessageAttributesType> = {}
): Promise<string> { ): Promise<string> {
const now = Date.now(); const now = Date.now();
const message: Partial<MessageAttributesType> = { const message: MessageAttributesType = {
id: generateGuid(),
conversationId: this.id, conversationId: this.id,
type, type,
sent_at: now, sent_at: now,
received_at: incrementMessageCounter(), received_at: incrementMessageCounter(),
received_at_ms: now, received_at_ms: now,
timestamp: now,
readStatus: ReadStatus.Read, readStatus: ReadStatus.Read,
seenStatus: SeenStatus.NotApplicable, seenStatus: SeenStatus.NotApplicable,
...extra, ...extra,
}; };
const id = await DataWriter.saveMessage( await DataWriter.saveMessage(message, {
// TODO: DESKTOP-722
message as MessageAttributesType,
{
ourAci: window.textsecure.storage.user.getCheckedAci(), ourAci: window.textsecure.storage.user.getCheckedAci(),
} });
); window.MessageCache.__DEPRECATED$register(
const model = window.MessageCache.__DEPRECATED$register( message.id,
id, message as MessageAttributesType,
new window.Whisper.Message({
...(message as MessageAttributesType),
id,
}),
'addNotification' 'addNotification'
); );
this.trigger('newmessage', model); this.trigger('newmessage', message);
return id; return message.id;
} }
async maybeSetPendingUniversalTimer( async maybeSetPendingUniversalTimer(
@ -3489,7 +3463,7 @@ export class ConversationModel extends window.Backbone
} }
async onReadMessage( async onReadMessage(
message: MessageModel, message: MessageAttributesType,
readAt?: number, readAt?: number,
newestSentAt?: number newestSentAt?: number
): Promise<void> { ): Promise<void> {
@ -3506,8 +3480,8 @@ export class ConversationModel extends window.Backbone
// sync. That's a notification explosion we don't need. // sync. That's a notification explosion we don't need.
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.received_at!, {
newestSentAt: newestSentAt || message.get('sent_at'), newestSentAt: newestSentAt || message.sent_at,
sendReadReceipts: false, sendReadReceipts: false,
readAt, readAt,
}) })
@ -3781,7 +3755,7 @@ export class ConversationModel extends window.Backbone
now, now,
extraReduxActions, extraReduxActions,
}: { }: {
message: MessageModel; message: MessageAttributesType;
dontAddMessage: boolean; dontAddMessage: boolean;
dontClearDraft: boolean; dontClearDraft: boolean;
now: number; now: number;
@ -3802,7 +3776,7 @@ export class ConversationModel extends window.Backbone
if (!dontAddMessage) { if (!dontAddMessage) {
this.doAddSingleMessage(message, { isJustSent: true }); this.doAddSingleMessage(message, { isJustSent: true });
} }
const notificationData = message.getNotificationData(); const notificationData = getNotificationDataForMessage(message);
const draftProperties = dontClearDraft const draftProperties = dontClearDraft
? {} ? {}
: { : {
@ -3811,14 +3785,16 @@ export class ConversationModel extends window.Backbone
draftBodyRanges: [], draftBodyRanges: [],
draftTimestamp: null, draftTimestamp: null,
quotedMessageId: undefined, quotedMessageId: undefined,
lastMessageAuthor: getMessageAuthorText(message.attributes), lastMessageAuthor: getMessageAuthorText(message),
lastMessageBodyRanges: message.get('bodyRanges'), lastMessageBodyRanges: message.bodyRanges,
lastMessage: lastMessage:
notificationData?.text || message.getNotificationText() || '', notificationData?.text ||
getNotificationTextForMessage(message) ||
'',
lastMessageStatus: 'sending' as const, lastMessageStatus: 'sending' as const,
}; };
const isEditMessage = Boolean(message.get('editHistory')); const isEditMessage = Boolean(message.editHistory);
this.set({ this.set({
...draftProperties, ...draftProperties,
@ -3988,17 +3964,16 @@ export class ConversationModel extends window.Backbone
storyId, storyId,
}); });
const model = new window.Whisper.Message(attributes); window.MessageCache.__DEPRECATED$register(
const message = window.MessageCache.__DEPRECATED$register( attributes.id,
model.id, attributes,
model,
'enqueueMessageForSend' 'enqueueMessageForSend'
); );
const dbStart = Date.now(); const dbStart = Date.now();
strictAssert( strictAssert(
typeof message.attributes.timestamp === 'number', typeof attributes.timestamp === 'number',
'Expected a timestamp' 'Expected a timestamp'
); );
@ -4006,14 +3981,14 @@ export class ConversationModel extends window.Backbone
{ {
type: conversationQueueJobEnum.enum.NormalMessage, type: conversationQueueJobEnum.enum.NormalMessage,
conversationId: this.id, conversationId: this.id,
messageId: message.id, messageId: attributes.id,
revision: this.get('revision'), revision: this.get('revision'),
}, },
async jobToInsert => { async jobToInsert => {
log.info( log.info(
`enqueueMessageForSend: saving message ${message.id} and job ${jobToInsert.id}` `enqueueMessageForSend: saving message ${attributes.id} and job ${jobToInsert.id}`
); );
await DataWriter.saveMessage(message.attributes, { await DataWriter.saveMessage(attributes, {
jobToInsert, jobToInsert,
forceSave: true, forceSave: true,
ourAci: window.textsecure.storage.user.getCheckedAci(), ourAci: window.textsecure.storage.user.getCheckedAci(),
@ -4032,14 +4007,14 @@ export class ConversationModel extends window.Backbone
const renderStart = Date.now(); const renderStart = Date.now();
// Perform asynchronous tasks before entering the batching mode // Perform asynchronous tasks before entering the batching mode
await this.beforeAddSingleMessage(model); await this.beforeAddSingleMessage(attributes);
if (sticker) { if (sticker) {
await addStickerPackReference(model.id, sticker.packId); await addStickerPackReference(attributes.id, sticker.packId);
} }
this.beforeMessageSend({ this.beforeMessageSend({
message: model, message: attributes,
dontClearDraft, dontClearDraft,
dontAddMessage: false, dontAddMessage: false,
now, now,
@ -4184,35 +4159,27 @@ export class ConversationModel extends window.Backbone
) )
); );
const { preview, activity } = stats; let { preview, activity } = stats;
let previewMessage: MessageModel | undefined;
let activityMessage: MessageModel | undefined;
// Register the message with MessageCache so that if it already exists // Get the in-memory message from MessageCache so that if it already exists
// in memory we use that data instead of the data from the db which may // in memory we use that data instead of the data from the db which may
// be out of date. // be out of date.
if (preview) { if (preview) {
previewMessage = window.MessageCache.__DEPRECATED$register( const inMemory = window.MessageCache.accessAttributes(preview.id);
preview.id, preview = inMemory || preview;
preview,
'previewMessage'
);
} }
if (activity) { if (activity) {
activityMessage = window.MessageCache.__DEPRECATED$register( const inMemory = window.MessageCache.accessAttributes(activity.id);
activity.id, activity = inMemory || activity;
activity,
'activityMessage'
);
} }
if ( if (
this.hasDraft() && this.hasDraft() &&
this.get('draftTimestamp') && this.get('draftTimestamp') &&
(!previewMessage || (!preview ||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
previewMessage.get('sent_at') < this.get('draftTimestamp')!) preview.sent_at < this.get('draftTimestamp')!)
) { ) {
return; return;
} }
@ -4220,38 +4187,37 @@ export class ConversationModel extends window.Backbone
let timestamp = this.get('timestamp') || null; let timestamp = this.get('timestamp') || null;
let lastMessageReceivedAt = this.get('lastMessageReceivedAt'); let lastMessageReceivedAt = this.get('lastMessageReceivedAt');
let lastMessageReceivedAtMs = this.get('lastMessageReceivedAtMs'); let lastMessageReceivedAtMs = this.get('lastMessageReceivedAtMs');
if (activityMessage) { if (activity) {
const callId = activityMessage.get('callId'); const { callId } = activity;
const callHistory = callId const callHistory = callId
? getCallHistorySelector(window.reduxStore.getState())(callId) ? getCallHistorySelector(window.reduxStore.getState())(callId)
: undefined; : undefined;
timestamp = timestamp = callHistory?.timestamp || activity.sent_at || timestamp;
callHistory?.timestamp || activityMessage.get('sent_at') || timestamp; lastMessageReceivedAt = activity.received_at || lastMessageReceivedAt;
lastMessageReceivedAt =
activityMessage.get('received_at') || lastMessageReceivedAt;
lastMessageReceivedAtMs = lastMessageReceivedAtMs =
activityMessage.get('received_at_ms') || lastMessageReceivedAtMs; activity.received_at_ms || lastMessageReceivedAtMs;
} }
const notificationData = previewMessage?.getNotificationData(); const notificationData = preview
? getNotificationDataForMessage(preview)
: undefined;
this.set({ this.set({
lastMessage: lastMessage:
notificationData?.text || previewMessage?.getNotificationText() || '', notificationData?.text ||
(preview ? getNotificationTextForMessage(preview) : undefined) ||
'',
lastMessageBodyRanges: notificationData?.bodyRanges, lastMessageBodyRanges: notificationData?.bodyRanges,
lastMessagePrefix: notificationData?.emoji, lastMessagePrefix: notificationData?.emoji,
lastMessageAuthor: getMessageAuthorText(previewMessage?.attributes), lastMessageAuthor: getMessageAuthorText(preview),
lastMessageStatus: lastMessageStatus:
(previewMessage (preview ? getMessagePropStatus(preview, ourConversationId) : null) ||
? getMessagePropStatus(previewMessage.attributes, ourConversationId) null,
: null) || null,
lastMessageReceivedAt, lastMessageReceivedAt,
lastMessageReceivedAtMs, lastMessageReceivedAtMs,
timestamp, timestamp,
lastMessageDeletedForEveryone: previewMessage lastMessageDeletedForEveryone: preview?.deletedForEveryone || false,
? previewMessage.get('deletedForEveryone')
: false,
}); });
await DataWriter.updateConversation(this.attributes); await DataWriter.updateConversation(this.attributes);
@ -4481,7 +4447,7 @@ export class ConversationModel extends window.Backbone
fromSync?: boolean; fromSync?: boolean;
isInitialSync?: boolean; isInitialSync?: boolean;
} }
): Promise<boolean | null | MessageModel | void> { ): Promise<void> {
const isSetByOther = providedSource || providedSentAt !== undefined; const isSetByOther = providedSource || providedSentAt !== undefined;
if (isSignalConversation(this.attributes)) { if (isSignalConversation(this.attributes)) {
@ -4500,7 +4466,7 @@ export class ConversationModel extends window.Backbone
createGroupChange: () => createGroupChange: () =>
this.updateExpirationTimerInGroupV2(providedExpireTimer), this.updateExpirationTimerInGroupV2(providedExpireTimer),
}); });
return false; return;
} }
if (!isSetByOther && this.isGroupV1AndDisabled()) { if (!isSetByOther && this.isGroupV1AndDisabled()) {
@ -4512,7 +4478,7 @@ export class ConversationModel extends window.Backbone
let expireTimer: DurationInSeconds | undefined = providedExpireTimer; let expireTimer: DurationInSeconds | undefined = providedExpireTimer;
let source = providedSource; let source = providedSource;
if (this.get('left')) { if (this.get('left')) {
return false; return;
} }
if (!expireTimer) { if (!expireTimer) {
@ -4522,7 +4488,7 @@ export class ConversationModel extends window.Backbone
this.get('expireTimer') === expireTimer || this.get('expireTimer') === expireTimer ||
(!expireTimer && !this.get('expireTimer')) (!expireTimer && !this.get('expireTimer'))
) { ) {
return null; return;
} }
const logId = const logId =
@ -4599,20 +4565,18 @@ export class ConversationModel extends window.Backbone
forceSave: true, forceSave: true,
}); });
const message = window.MessageCache.__DEPRECATED$register( window.MessageCache.__DEPRECATED$register(
id, id,
new window.Whisper.Message(attributes), attributes,
'updateExpirationTimer' 'updateExpirationTimer'
); );
void this.addSingleMessage(message); void this.addSingleMessage(attributes);
void this.updateUnread(); void this.updateUnread();
log.info( log.info(
`${logId}: added a notification received_at=${message.get('received_at')}` `${logId}: added a notification received_at=${attributes.received_at}`
); );
return message;
} }
isSealedSenderDisabled(): boolean { isSealedSenderDisabled(): boolean {
@ -4754,10 +4718,10 @@ export class ConversationModel extends window.Backbone
// first/last name in their profile data. // first/last name in their profile data.
const nameChanged = oldName !== newName; const nameChanged = oldName !== newName;
if (!isMe(this.attributes) && hadPreviousName && nameChanged) { if (!isMe(this.attributes) && hadPreviousName && nameChanged) {
const change = { const change: ProfileNameChangeType = {
type: 'name', type: 'name',
oldName, oldName: oldName ?? '',
newName, newName: newName ?? '',
}; };
await this.addProfileChange(change); await this.addProfileChange(change);
@ -5247,7 +5211,7 @@ export class ConversationModel extends window.Backbone
} }
async notify( async notify(
message: Readonly<MessageModel>, message: Readonly<MessageAttributesType>,
reaction?: Readonly<ReactionAttributesType> reaction?: Readonly<ReactionAttributesType>
): Promise<void> { ): Promise<void> {
// As a performance optimization don't perform any work if notifications are // As a performance optimization don't perform any work if notifications are
@ -5265,7 +5229,7 @@ export class ConversationModel extends window.Backbone
const ourPni = window.textsecure.storage.user.getCheckedPni(); const ourPni = window.textsecure.storage.user.getCheckedPni();
const ourServiceIds: Set<ServiceIdString> = new Set([ourAci, ourPni]); const ourServiceIds: Set<ServiceIdString> = new Set([ourAci, ourPni]);
const mentionsMe = (message.get('bodyRanges') || []).some(bodyRange => { const mentionsMe = (message.bodyRanges || []).some(bodyRange => {
if (!BodyRange.isMention(bodyRange)) { if (!BodyRange.isMention(bodyRange)) {
return false; return false;
} }
@ -5278,7 +5242,7 @@ export class ConversationModel extends window.Backbone
} }
} }
if (!isIncoming(message.attributes) && !reaction) { if (!isIncoming(message) && !reaction) {
return; return;
} }
@ -5287,7 +5251,7 @@ export class ConversationModel extends window.Backbone
const sender = reaction const sender = reaction
? window.ConversationController.get(reaction.fromId) ? window.ConversationController.get(reaction.fromId)
: getAuthor(message.attributes); : getAuthor(message);
const senderName = sender const senderName = sender
? sender.getTitle() ? sender.getTitle()
: window.i18n('icu:unknownContact'); : window.i18n('icu:unknownContact');
@ -5300,20 +5264,17 @@ export class ConversationModel extends window.Backbone
const { url, absolutePath } = await this.getAvatarOrIdenticon(); const { url, absolutePath } = await this.getAvatarOrIdenticon();
const messageJSON = message.toJSON();
const messageId = message.id; const messageId = message.id;
const isExpiringMessage = Message.hasExpiration(messageJSON); const isExpiringMessage = hasExpiration(message);
notificationService.add({ notificationService.add({
senderTitle, senderTitle,
conversationId, conversationId,
storyId: isMessageInDirectConversation storyId: isMessageInDirectConversation ? undefined : message.storyId,
? undefined
: message.get('storyId'),
notificationIconUrl: url, notificationIconUrl: url,
notificationIconAbsolutePath: absolutePath, notificationIconAbsolutePath: absolutePath,
isExpiringMessage, isExpiringMessage,
message: message.getNotificationText(), message: getNotificationTextForMessage(message),
messageId, messageId,
reaction: reaction reaction: reaction
? { ? {
@ -5322,7 +5283,7 @@ export class ConversationModel extends window.Backbone
targetTimestamp: reaction.targetTimestamp, targetTimestamp: reaction.targetTimestamp,
} }
: undefined, : undefined,
sentAt: message.get('timestamp'), sentAt: message.timestamp,
type: reaction ? NotificationType.Reaction : NotificationType.Message, type: reaction ? NotificationType.Reaction : NotificationType.Message,
}); });
} }

View file

@ -2126,10 +2126,10 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
return; return;
} }
conversation.trigger('newmessage', this); conversation.trigger('newmessage', this.attributes);
if (await shouldReplyNotifyUser(this.attributes, conversation)) { if (await shouldReplyNotifyUser(this.attributes, conversation)) {
await conversation.notify(this); await conversation.notify(this.attributes);
} }
// Increment the sent message count if this is an outgoing message // Increment the sent message count if this is an outgoing message
@ -2306,16 +2306,18 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
timestamp: reaction.timestamp, timestamp: reaction.timestamp,
}); });
const messageToAdd = window.MessageCache.__DEPRECATED$register( window.MessageCache.__DEPRECATED$register(
generatedMessage.id, generatedMessage.id,
generatedMessage, generatedMessage,
'generatedMessage' 'generatedMessage'
); );
if (isDirectConversation(targetConversation.attributes)) { if (isDirectConversation(targetConversation.attributes)) {
await targetConversation.addSingleMessage(messageToAdd); await targetConversation.addSingleMessage(
generatedMessage.attributes
);
if (!targetConversation.get('active_at')) { if (!targetConversation.get('active_at')) {
targetConversation.set({ targetConversation.set({
active_at: messageToAdd.get('timestamp'), active_at: generatedMessage.attributes.timestamp,
}); });
await DataWriter.updateConversation(targetConversation.attributes); await DataWriter.updateConversation(targetConversation.attributes);
} }
@ -2328,11 +2330,11 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
); );
if ( if (
await shouldReplyNotifyUser( await shouldReplyNotifyUser(
messageToAdd.attributes, generatedMessage.attributes,
targetConversation targetConversation
) )
) { ) {
drop(targetConversation.notify(messageToAdd)); drop(targetConversation.notify(generatedMessage.attributes));
} }
} }
} }
@ -2403,7 +2405,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
this.set({ reactions }); this.set({ reactions });
if (isOutgoing(this.attributes) && isFromSomeoneElse) { if (isOutgoing(this.attributes) && isFromSomeoneElse) {
void conversation.notify(this, reaction); void conversation.notify(this.attributes, reaction);
} }
} }
} }
@ -2465,14 +2467,14 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
forceSave: true, forceSave: true,
}); });
void conversation.addSingleMessage(
window.MessageCache.__DEPRECATED$register( window.MessageCache.__DEPRECATED$register(
generatedMessage.id, generatedMessage.id,
generatedMessage, generatedMessage,
'generatedMessage2' 'generatedMessage2'
)
); );
void conversation.addSingleMessage(generatedMessage.attributes);
jobData = { jobData = {
type: conversationQueueJobEnum.enum.NormalMessage, type: conversationQueueJobEnum.enum.NormalMessage,
conversationId: conversation.id, conversationId: conversation.id,

View file

@ -13,11 +13,9 @@ export function getMentionsRegex(): RegExp {
} }
export type Message = ( export type Message = (
| UserMessage
| VerifiedChangeMessage | VerifiedChangeMessage
| ProfileChangeNotificationMessage | ProfileChangeNotificationMessage
) & { deletedForEveryone?: boolean }; ) & { deletedForEveryone?: boolean };
export type UserMessage = IncomingMessage | OutgoingMessage;
export type IncomingMessage = Readonly< export type IncomingMessage = Readonly<
{ {
@ -109,16 +107,3 @@ export type MessageSchemaVersion6 = Partial<
contact: Array<EmbeddedContactType>; contact: Array<EmbeddedContactType>;
}> }>
>; >;
export const isUserMessage = (message: Message): message is UserMessage =>
message.type === 'incoming' || message.type === 'outgoing';
export const hasExpiration = (message: Message): boolean => {
if (!isUserMessage(message)) {
return false;
}
const { expireTimer } = message;
return typeof expireTimer === 'number' && expireTimer > 0;
};

View file

@ -46,8 +46,6 @@ import {
import { encryptLegacyAttachment } from '../util/encryptLegacyAttachment'; import { encryptLegacyAttachment } from '../util/encryptLegacyAttachment';
import { deepClone } from '../util/deepClone'; import { deepClone } from '../util/deepClone';
export { hasExpiration } from './Message';
export const GROUP = 'group'; export const GROUP = 'group';
export const PRIVATE = 'private'; export const PRIVATE = 'private';
@ -1039,3 +1037,16 @@ async function deletePreviews(
}) })
); );
} }
export const isUserMessage = (message: MessageAttributesType): boolean =>
message.type === 'incoming' || message.type === 'outgoing';
export const hasExpiration = (message: MessageAttributesType): boolean => {
if (!isUserMessage(message)) {
return false;
}
const { expireTimer } = message;
return typeof expireTimer === 'number' && expireTimer > 0;
};

View file

@ -1153,19 +1153,16 @@ async function saveCallHistory({
callId: callHistory.callId, callId: callHistory.callId,
}; };
const id = await DataWriter.saveMessage(message, { message.id = await DataWriter.saveMessage(message, {
ourAci: window.textsecure.storage.user.getCheckedAci(), ourAci: window.textsecure.storage.user.getCheckedAci(),
// We don't want to force save if we're updating an existing message // We don't want to force save if we're updating an existing message
forceSave: prevMessage == null, forceSave: prevMessage == null,
}); });
log.info('saveCallHistory: Saved call history message:', id); log.info('saveCallHistory: Saved call history message:', message.id);
const model = window.MessageCache.__DEPRECATED$register( window.MessageCache.__DEPRECATED$register(
id, message.id,
new window.Whisper.Message({ message,
...message,
id,
}),
'callDisposition' 'callDisposition'
); );
@ -1175,7 +1172,7 @@ async function saveCallHistory({
} else { } else {
conversation.incrementMessageCount(); conversation.incrementMessageCount();
} }
conversation.trigger('newmessage', model); conversation.trigger('newmessage', message);
} }
await conversation.updateLastMessage().catch(error => { await conversation.updateLastMessage().catch(error => {

View file

@ -50,17 +50,20 @@ export function getTargetOfThisEditTimestamp({
return originalTimestamp; return originalTimestamp;
} }
export function getPropForTimestamp<T extends keyof EditHistoryType>({ export function getPropForTimestamp<
Attrs extends ReadonlyMessageAttributesType,
T extends keyof EditHistoryType,
>({
log, log,
message, message,
prop, prop,
targetTimestamp, targetTimestamp,
}: { }: {
log: LoggerType; log: LoggerType;
message: MessageAttributesType; message: Attrs;
prop: T; prop: T;
targetTimestamp: number; targetTimestamp: number;
}): EditHistoryType[T] { }): Attrs[T] {
const logId = `getPropForTimestamp(${targetTimestamp}})`; const logId = `getPropForTimestamp(${targetTimestamp}})`;
const { editHistory } = message; const { editHistory } = message;
@ -74,7 +77,7 @@ export function getPropForTimestamp<T extends keyof EditHistoryType>({
return message[prop]; return message[prop];
} }
return targetEdit[prop]; return targetEdit[prop] as Attrs[T];
} }
export function getChangesForPropAtTimestamp<T extends keyof EditHistoryType>({ export function getChangesForPropAtTimestamp<T extends keyof EditHistoryType>({

View file

@ -19,7 +19,7 @@ export async function hydrateStoryContext(
shouldSave?: boolean; shouldSave?: boolean;
isStoryErased?: boolean; isStoryErased?: boolean;
} = {} } = {}
): Promise<void> { ): Promise<Partial<MessageAttributesType> | undefined> {
let messageAttributes: MessageAttributesType; let messageAttributes: MessageAttributesType;
try { try {
messageAttributes = await window.MessageCache.resolveAttributes( messageAttributes = await window.MessageCache.resolveAttributes(
@ -27,12 +27,12 @@ export async function hydrateStoryContext(
messageId messageId
); );
} catch { } catch {
return; return undefined;
} }
const { storyId } = messageAttributes; const { storyId } = messageAttributes;
if (!storyId) { if (!storyId) {
return; return undefined;
} }
const { storyReplyContext: context } = messageAttributes; const { storyReplyContext: context } = messageAttributes;
@ -42,7 +42,7 @@ export async function hydrateStoryContext(
context && context &&
(context.attachment?.url || !context.messageId) (context.attachment?.url || !context.messageId)
) { ) {
return; return undefined;
} }
let storyMessage: MessageAttributesType | undefined; let storyMessage: MessageAttributesType | undefined;
@ -88,7 +88,7 @@ export async function hydrateStoryContext(
}); });
} }
return; return newMessageAttributes;
} }
const attachments = getAttachmentsForMessage({ ...storyMessage }); const attachments = getAttachmentsForMessage({ ...storyMessage });
@ -119,4 +119,5 @@ export async function hydrateStoryContext(
skipSaveToDatabase: true, skipSaveToDatabase: true,
}); });
} }
return newMessageAttributes;
} }

View file

@ -102,7 +102,7 @@ export async function modifyTargetMessage(
} }
if (type === 'outgoing' || (type === 'story' && ourAci === sourceServiceId)) { if (type === 'outgoing' || (type === 'story' && ourAci === sourceServiceId)) {
const receipts = await MessageReceipts.forMessage(message); const receipts = await MessageReceipts.forMessage(message.attributes);
const sendActions = receipts.map(({ receiptSync }) => { const sendActions = receipts.map(({ receiptSync }) => {
let sendActionType: SendActionType; let sendActionType: SendActionType;
const receiptType = receiptSync.type; const receiptType = receiptSync.type;
@ -164,10 +164,10 @@ export async function modifyTargetMessage(
if (type === 'incoming') { if (type === 'incoming') {
// In a followup (see DESKTOP-2100), we want to make `ReadSyncs#forMessage` return // In a followup (see DESKTOP-2100), we want to make `ReadSyncs#forMessage` return
// an array, not an object. This array wrapping makes that future a bit easier. // an array, not an object. This array wrapping makes that future a bit easier.
const maybeSingleReadSync = await ReadSyncs.forMessage(message); const maybeSingleReadSync = await ReadSyncs.forMessage(message.attributes);
const readSyncs = maybeSingleReadSync ? [maybeSingleReadSync] : []; const readSyncs = maybeSingleReadSync ? [maybeSingleReadSync] : [];
const viewSyncs = await ViewSyncs.forMessage(message); const viewSyncs = await ViewSyncs.forMessage(message.attributes);
const isGroupStoryReply = const isGroupStoryReply =
isGroup(conversation.attributes) && message.get('storyId'); isGroup(conversation.attributes) && message.get('storyId');
@ -233,13 +233,13 @@ export async function modifyTargetMessage(
drop( drop(
message message
.getConversation() .getConversation()
?.onReadMessage(message, markReadAt, newestSentAt) ?.onReadMessage(message.attributes, markReadAt, newestSentAt)
); );
} }
// Check for out-of-order view once open syncs // Check for out-of-order view once open syncs
if (isTapToView(message.attributes)) { if (isTapToView(message.attributes)) {
const viewOnceOpenSync = ViewOnceOpenSyncs.forMessage(message); const viewOnceOpenSync = ViewOnceOpenSyncs.forMessage(message.attributes);
if (viewOnceOpenSync) { if (viewOnceOpenSync) {
await message.markViewOnceMessageViewed({ fromSync: true }); await message.markViewOnceMessageViewed({ fromSync: true });
changed = true; changed = true;
@ -248,7 +248,7 @@ export async function modifyTargetMessage(
} }
if (isStory(message.attributes)) { if (isStory(message.attributes)) {
const viewSyncs = await ViewSyncs.forMessage(message); const viewSyncs = await ViewSyncs.forMessage(message.attributes);
if (viewSyncs.length !== 0) { if (viewSyncs.length !== 0) {
message.set({ message.set({
@ -277,7 +277,7 @@ export async function modifyTargetMessage(
} }
// Does message message have any pending, previously-received associated reactions? // Does message message have any pending, previously-received associated reactions?
const reactions = Reactions.findReactionsForMessage(message); const reactions = Reactions.findReactionsForMessage(message.attributes);
log.info( log.info(
`${logId}: Found ${reactions.length} early reaction(s) for ${message.attributes.type} message` `${logId}: Found ${reactions.length} early reaction(s) for ${message.attributes.type} message`

View file

@ -239,7 +239,7 @@ export async function sendEditedMessage(
SEND_REPORT_THRESHOLD_MS, SEND_REPORT_THRESHOLD_MS,
async () => { async () => {
conversation.beforeMessageSend({ conversation.beforeMessageSend({
message: targetMessage, message: targetMessage.attributes,
dontClearDraft: false, dontClearDraft: false,
dontAddMessage: true, dontAddMessage: true,
now: timestamp, now: timestamp,

View file

@ -307,20 +307,17 @@ export async function sendStoryMessage(
// * Save the message model // * Save the message model
// * Add the message to the conversation // * Add the message to the conversation
await Promise.all( await Promise.all(
distributionListMessages.map(messageAttributes => { distributionListMessages.map(message => {
const model = new window.Whisper.Message(messageAttributes); window.MessageCache.__DEPRECATED$register(
const message = window.MessageCache.__DEPRECATED$register( message.id,
model.id, new window.Whisper.Message(message),
model,
'sendStoryMessage' 'sendStoryMessage'
); );
void ourConversation.addSingleMessage(model, { isJustSent: true }); void ourConversation.addSingleMessage(message, { isJustSent: true });
log.info( log.info(`stories.sendStoryMessage: saving message ${message.timestamp}`);
`stories.sendStoryMessage: saving message ${messageAttributes.timestamp}` return DataWriter.saveMessage(message, {
);
return DataWriter.saveMessage(message.attributes, {
forceSave: true, forceSave: true,
ourAci: window.textsecure.storage.user.getCheckedAci(), ourAci: window.textsecure.storage.user.getCheckedAci(),
}); });
@ -362,20 +359,21 @@ export async function sendStoryMessage(
timestamp: messageAttributes.timestamp, timestamp: messageAttributes.timestamp,
}, },
async jobToInsert => { async jobToInsert => {
const model = new window.Whisper.Message(messageAttributes); window.MessageCache.__DEPRECATED$register(
const message = window.MessageCache.__DEPRECATED$register( messageAttributes.id,
model.id, new window.Whisper.Message(messageAttributes),
model,
'sendStoryMessage' 'sendStoryMessage'
); );
const conversation =
const conversation = message.getConversation(); window.ConversationController.get(conversationId);
void conversation?.addSingleMessage(model, { isJustSent: true }); void conversation?.addSingleMessage(messageAttributes, {
isJustSent: true,
});
log.info( log.info(
`stories.sendStoryMessage: saving message ${messageAttributes.timestamp}` `stories.sendStoryMessage: saving message ${messageAttributes.timestamp}`
); );
await DataWriter.saveMessage(message.attributes, { await DataWriter.saveMessage(messageAttributes, {
forceSave: true, forceSave: true,
jobToInsert, jobToInsert,
ourAci: window.textsecure.storage.user.getCheckedAci(), ourAci: window.textsecure.storage.user.getCheckedAci(),