cleanup models with unused events

also, sort message from DB and on redux by sent_at or received_at when
not a public group
pull/1495/head
Audric Ackermann 4 years ago
parent 6edcb88788
commit ea2c4437a3
No known key found for this signature in database
GPG Key ID: 999F434D76324AD4

@ -2472,6 +2472,8 @@ async function getUnreadCountByConversation(conversationId) {
} }
// Note: Sorting here is necessary for getting the last message (with limit 1) // Note: Sorting here is necessary for getting the last message (with limit 1)
// be sure to update the sorting order to sort messages on reduxz too (sortMessages
async function getMessagesByConversation( async function getMessagesByConversation(
conversationId, conversationId,
{ limit = 100, receivedAt = Number.MAX_VALUE, type = '%' } = {} { limit = 100, receivedAt = Number.MAX_VALUE, type = '%' } = {}
@ -2482,7 +2484,7 @@ async function getMessagesByConversation(
conversationId = $conversationId AND conversationId = $conversationId AND
received_at < $received_at AND received_at < $received_at AND
type LIKE $type type LIKE $type
ORDER BY serverTimestamp DESC, serverId DESC, sent_at DESC ORDER BY serverTimestamp DESC, serverId DESC, sent_at DESC, received_at DESC
LIMIT $limit; LIMIT $limit;
`, `,
{ {

@ -24,6 +24,7 @@ export interface LokiAppDotNetServerInterface {
} }
export interface LokiPublicChannelAPI { export interface LokiPublicChannelAPI {
banUser(source: string): Promise<boolean>;
getModerators: () => Promise<Array<string>>; getModerators: () => Promise<Array<string>>;
serverAPI: any; serverAPI: any;
deleteMessages(arg0: any[]); deleteMessages(arg0: any[]);

@ -88,11 +88,6 @@
const force = true; const force = true;
await message.setToExpire(force); await message.setToExpire(force);
const conversation = message.getConversation();
if (conversation) {
conversation.trigger('expiration-change', message);
}
} }
this.remove(receipt); this.remove(receipt);

@ -11,7 +11,7 @@ import {
ReadReceiptMessage, ReadReceiptMessage,
TypingMessage, TypingMessage,
} from '../session/messages/outgoing'; } from '../session/messages/outgoing';
import { ClosedGroupChatMessage } from '../session/messages/outgoing/content/data/group'; import { ClosedGroupChatMessage } from '../session/messages/outgoing/content/data/group/ClosedGroupChatMessage';
import { OpenGroup, PubKey } from '../session/types'; import { OpenGroup, PubKey } from '../session/types';
import { ToastUtils, UserUtils } from '../session/utils'; import { ToastUtils, UserUtils } from '../session/utils';
import { BlockedNumberController } from '../util'; import { BlockedNumberController } from '../util';
@ -20,7 +20,7 @@ import { leaveClosedGroup } from '../session/group';
import { SignalService } from '../protobuf'; import { SignalService } from '../protobuf';
import { MessageCollection, MessageModel } from './message'; import { MessageCollection, MessageModel } from './message';
import * as Data from '../../js/modules/data'; import * as Data from '../../js/modules/data';
import { MessageAttributesOptionals } from './messageType'; import { MessageAttributesOptionals, MessageModelType } from './messageType';
import autoBind from 'auto-bind'; import autoBind from 'auto-bind';
export interface OurLokiProfile { export interface OurLokiProfile {
@ -160,15 +160,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
this.bouncyUpdateLastMessage.bind(this), this.bouncyUpdateLastMessage.bind(this),
1000 1000
); );
// this.listenTo(
// this.messageCollection,
// 'add remove destroy',
// debouncedUpdateLastMessage
// );
// Listening for out-of-band data updates // Listening for out-of-band data updates
this.on('delivered', this.updateAndMerge);
this.on('read', this.updateAndMerge);
this.on('expiration-change', this.updateAndMerge);
this.on('expired', this.onExpired); this.on('expired', this.onExpired);
this.on('ourAvatarChanged', avatar => this.on('ourAvatarChanged', avatar =>
@ -370,21 +362,6 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
} }
} }
public async updateAndMerge(message: any) {
await this.updateLastMessage();
const mergeMessage = () => {
const existing = this.messageCollection.get(message.id);
if (!existing) {
return;
}
existing.merge(message.attributes);
};
mergeMessage();
}
public async onExpired(message: any) { public async onExpired(message: any) {
await this.updateLastMessage(); await this.updateLastMessage();
@ -439,16 +416,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
await model.setServerTimestamp(serverTimestamp); await model.setServerTimestamp(serverTimestamp);
return undefined; return undefined;
} }
public addSingleMessage(
message: MessageAttributesOptionals,
setToExpire = true
) {
const model = this.messageCollection.add(message, { merge: true });
if (setToExpire) {
void model.setToExpire();
}
return model;
}
public format() { public format() {
return this.cachedProps; return this.cachedProps;
} }
@ -673,17 +641,23 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
conversationId: this.id, conversationId: this.id,
}); });
} }
public async sendMessageJob(message: any) { public async sendMessageJob(message: MessageModel) {
try { try {
const uploads = await message.uploadData(); const uploads = await message.uploadData();
const { id } = message; const { id } = message;
const expireTimer = this.get('expireTimer'); const expireTimer = this.get('expireTimer');
const destination = this.id; const destination = this.id;
const sentAt = message.get('sent_at');
if (!sentAt) {
throw new Error('sendMessageJob() sent_at must be set.');
}
const chatMessage = new ChatMessage({ const chatMessage = new ChatMessage({
body: uploads.body, body: uploads.body,
identifier: id, identifier: id,
timestamp: message.get('sent_at'), timestamp: sentAt,
attachments: uploads.attachments, attachments: uploads.attachments,
expireTimer, expireTimer,
preview: uploads.preview, preview: uploads.preview,
@ -696,7 +670,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
const openGroupParams = { const openGroupParams = {
body: uploads.body, body: uploads.body,
timestamp: message.get('sent_at'), timestamp: sentAt,
group: openGroup, group: openGroup,
attachments: uploads.attachments, attachments: uploads.attachments,
preview: uploads.preview, preview: uploads.preview,
@ -716,7 +690,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
const groupInvitation = message.get('groupInvitation'); const groupInvitation = message.get('groupInvitation');
const groupInvitMessage = new GroupInvitationMessage({ const groupInvitMessage = new GroupInvitationMessage({
identifier: id, identifier: id,
timestamp: message.get('sent_at'), timestamp: sentAt,
serverName: groupInvitation.name, serverName: groupInvitation.name,
channelId: groupInvitation.channelId, channelId: groupInvitation.channelId,
serverAddress: groupInvitation.address, serverAddress: groupInvitation.address,
@ -767,6 +741,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
this.clearTypingTimers(); this.clearTypingTimers();
const destination = this.id; const destination = this.id;
const isPrivate = this.isPrivate();
const expireTimer = this.get('expireTimer'); const expireTimer = this.get('expireTimer');
const recipients = this.getRecipients(); const recipients = this.getRecipients();
@ -808,28 +783,16 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
const attributes: MessageAttributesOptionals = { const attributes: MessageAttributesOptionals = {
...messageWithSchema, ...messageWithSchema,
groupInvitation, groupInvitation,
id: window.getGuid(),
conversationId: this.id, conversationId: this.id,
destination: isPrivate ? destination : undefined,
}; };
const model = this.addSingleMessage(attributes); const model = await this.addSingleMessage(attributes);
MessageController.getInstance().register(model.id, model);
const id = await model.commit();
model.set({ id });
if (this.isPrivate()) {
model.set({ destination });
}
if (this.isPublic()) { if (this.isPublic()) {
await model.setServerTimestamp(new Date().getTime()); await model.setServerTimestamp(new Date().getTime());
} }
window.Whisper.events.trigger('messageAdded', {
conversationKey: this.id,
messageModel: model,
});
this.set({ this.set({
lastMessage: model.getNotificationText(), lastMessage: model.getNotificationText(),
lastMessageStatus: 'sending', lastMessageStatus: 'sending',
@ -912,7 +875,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
public async updateExpirationTimer( public async updateExpirationTimer(
providedExpireTimer: any, providedExpireTimer: any,
providedSource?: string, providedSource?: string,
receivedAt?: number, receivedAt?: number, // is set if it comes from outside
options: any = {} options: any = {}
) { ) {
let expireTimer = providedExpireTimer; let expireTimer = providedExpireTimer;
@ -936,6 +899,8 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
source, source,
}); });
const isOutgoing = Boolean(receivedAt);
source = source || UserUtils.getOurPubKeyStrFromCache(); source = source || UserUtils.getOurPubKeyStrFromCache();
// When we add a disappearing messages notification to the conversation, we want it // When we add a disappearing messages notification to the conversation, we want it
@ -943,9 +908,8 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
const timestamp = (receivedAt || Date.now()) - 1; const timestamp = (receivedAt || Date.now()) - 1;
this.set({ expireTimer }); this.set({ expireTimer });
await this.commit();
const message = new MessageModel({ const messageAttributes = {
// Even though this isn't reflected to the user, we want to place the last seen // Even though this isn't reflected to the user, we want to place the last seen
// indicator above it. We set it to 'unread' to trigger that placement. // indicator above it. We set it to 'unread' to trigger that placement.
unread: true, unread: true,
@ -961,23 +925,14 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
fromGroupUpdate: options.fromGroupUpdate, fromGroupUpdate: options.fromGroupUpdate,
}, },
expireTimer: 0, expireTimer: 0,
type: 'incoming', type: isOutgoing ? 'outgoing' : ('incoming' as MessageModelType),
}); destination: this.id,
recipients: isOutgoing ? this.getRecipients() : undefined,
message.set({ destination: this.id }); };
if (message.isOutgoing()) {
message.set({ recipients: this.getRecipients() });
}
const id = await message.commit(); const message = await this.addSingleMessage(messageAttributes);
message.set({ id });
window.Whisper.events.trigger('messageAdded', {
conversationKey: this.id,
messageModel: message,
});
// tell the UI this conversation was updated
await this.commit(); await this.commit();
// if change was made remotely, don't send it to the number/group // if change was made remotely, don't send it to the number/group
@ -991,7 +946,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
} }
const expireUpdate = { const expireUpdate = {
identifier: id, identifier: message.id,
timestamp, timestamp,
expireTimer, expireTimer,
profileKey, profileKey,
@ -1008,13 +963,16 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
return message.sendSyncMessageOnly(expirationTimerMessage); return message.sendSyncMessageOnly(expirationTimerMessage);
} }
if (this.get('type') === 'private') { if (this.isPrivate()) {
const expirationTimerMessage = new ExpirationTimerUpdateMessage( const expirationTimerMessage = new ExpirationTimerUpdateMessage(
expireUpdate expireUpdate
); );
const pubkey = new PubKey(this.get('id')); const pubkey = new PubKey(this.get('id'));
await getMessageQueue().sendToPubKey(pubkey, expirationTimerMessage); await getMessageQueue().sendToPubKey(pubkey, expirationTimerMessage);
} else { } else {
window.log.warn(
'TODO: Expiration update for closed groups are to be updated'
);
const expireUpdateForGroup = { const expireUpdateForGroup = {
...expireUpdate, ...expireUpdate,
groupId: this.get('id'), groupId: this.get('id'),
@ -1023,24 +981,12 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
const expirationTimerMessage = new ExpirationTimerUpdateMessage( const expirationTimerMessage = new ExpirationTimerUpdateMessage(
expireUpdateForGroup expireUpdateForGroup
); );
// special case when we are the only member of a closed group
const ourNumber = UserUtils.getOurPubKeyStrFromCache();
if (
this.get('members').length === 1 &&
this.get('members')[0] === ourNumber
) {
return message.sendSyncMessageOnly(expirationTimerMessage);
}
await getMessageQueue().sendToGroup(expirationTimerMessage); await getMessageQueue().sendToGroup(expirationTimerMessage);
} }
return message; return message;
} }
public isSearchable() {
return !this.get('left');
}
public async commit() { public async commit() {
await window.Signal.Data.updateConversation(this.id, this.attributes, { await window.Signal.Data.updateConversation(this.id, this.attributes, {
Conversation: ConversationModel, Conversation: ConversationModel,
@ -1048,27 +994,33 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
this.trigger('change', this); this.trigger('change', this);
} }
public async addMessage(messageAttributes: MessageAttributesOptionals) { public async addSingleMessage(
messageAttributes: MessageAttributesOptionals,
setToExpire = true
) {
const model = new MessageModel(messageAttributes); const model = new MessageModel(messageAttributes);
const messageId = await model.commit(); const messageId = await model.commit();
model.set({ id: messageId }); model.set({ id: messageId });
if (setToExpire) {
await model.setToExpire();
}
MessageController.getInstance().register(messageId, model);
window.Whisper.events.trigger('messageAdded', { window.Whisper.events.trigger('messageAdded', {
conversationKey: this.id, conversationKey: this.id,
messageModel: model, messageModel: model,
}); });
return model; return model;
} }
public async leaveGroup() { public async leaveGroup() {
if (this.get('type') !== ConversationType.GROUP) {
window.log.error('Cannot leave a non-group conversation');
return;
}
if (this.isMediumGroup()) { if (this.isMediumGroup()) {
await leaveClosedGroup(this.id); await leaveClosedGroup(this.id);
} else { } else {
window.log.error('Cannot leave a non-medium group conversation');
throw new Error( throw new Error(
'Legacy group are not supported anymore. You need to create this group again.' 'Legacy group are not supported anymore. You need to create this group again.'
); );

@ -11,7 +11,7 @@ import {
DataMessage, DataMessage,
OpenGroupMessage, OpenGroupMessage,
} from '../../ts/session/messages/outgoing'; } from '../../ts/session/messages/outgoing';
import { ClosedGroupChatMessage } from '../../ts/session/messages/outgoing/content/data/group'; import { ClosedGroupChatMessage } from '../../ts/session/messages/outgoing/content/data/group/ClosedGroupChatMessage';
import { EncryptionType, PubKey } from '../../ts/session/types'; import { EncryptionType, PubKey } from '../../ts/session/types';
import { ToastUtils, UserUtils } from '../../ts/session/utils'; import { ToastUtils, UserUtils } from '../../ts/session/utils';
import { import {
@ -41,9 +41,6 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
); );
} }
this.on('destroy', this.onDestroy);
this.on('change:expirationStartTimestamp', this.setToExpire);
this.on('change:expireTimer', this.setToExpire);
// this.on('expired', this.onExpired); // this.on('expired', this.onExpired);
void this.setToExpire(); void this.setToExpire();
autoBind(this); autoBind(this);
@ -674,7 +671,9 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
? [this.get('source')] ? [this.get('source')]
: _.union( : _.union(
this.get('sent_to') || [], this.get('sent_to') || [],
this.get('recipients') || this.getConversation().getRecipients() this.get('recipients') ||
this.getConversation()?.getRecipients() ||
[]
); );
// This will make the error message for outgoing key errors a bit nicer // This will make the error message for outgoing key errors a bit nicer
@ -750,8 +749,20 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
resolve: async () => { resolve: async () => {
const source = this.get('source'); const source = this.get('source');
const conversation = this.getConversation(); const conversation = this.getConversation();
if (!conversation) {
window.log.info(
'cannot ban user, the corresponding conversation was not found.'
);
return;
}
const channelAPI = await conversation.getPublicSendData(); const channelAPI = await conversation.getPublicSendData();
if (!channelAPI) {
window.log.info(
'cannot ban user, the corresponding channelAPI was not found.'
);
return;
}
const success = await channelAPI.banUser(source); const success = await channelAPI.banUser(source);
if (success) { if (success) {
@ -805,7 +816,8 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
const conversation = this.getConversation(); const conversation = this.getConversation();
const openGroup = const openGroup =
conversation && conversation.isPublic() && conversation.toOpenGroup(); (conversation && conversation.isPublic() && conversation.toOpenGroup()) ||
undefined;
const { AttachmentUtils } = Utils; const { AttachmentUtils } = Utils;
const [attachments, preview, quote] = await Promise.all([ const [attachments, preview, quote] = await Promise.all([
@ -836,9 +848,12 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
await this.commit(); await this.commit();
try { try {
const conversation = this.getConversation(); const conversation = this.getConversation();
const intendedRecipients = this.get('recipients') || []; if (!conversation) {
const successfulRecipients = this.get('sent_to') || []; window.log.info(
const currentRecipients = conversation.getRecipients(); 'cannot retry send message, the corresponding conversation was not found.'
);
return;
}
if (conversation.isPublic()) { if (conversation.isPublic()) {
const openGroup = { const openGroup = {
@ -858,19 +873,8 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
return getMessageQueue().sendToGroup(openGroupMessage); return getMessageQueue().sendToGroup(openGroupMessage);
} }
let recipients = _.intersection(intendedRecipients, currentRecipients);
recipients = recipients.filter(
key => !successfulRecipients.includes(key)
);
if (!recipients.length) {
window.log.warn('retrySend: Nobody to send to!');
return this.commit();
}
const { body, attachments, preview, quote } = await this.uploadData(); const { body, attachments, preview, quote } = await this.uploadData();
const ourNumber = window.storage.get('primaryDevicePubKey'); const ourNumber = UserUtils.getOurPubKeyStrFromCache();
const ourConversation = ConversationController.getInstance().get( const ourConversation = ConversationController.getInstance().get(
ourNumber ourNumber
); );
@ -893,86 +897,33 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
const chatMessage = new ChatMessage(chatParams); const chatMessage = new ChatMessage(chatParams);
// Special-case the self-send case - we send only a sync message // Special-case the self-send case - we send only a sync message
if (recipients.length === 1) { if (conversation.isMe()) {
const isOurDevice = UserUtils.isUsFromCache(recipients[0]); return this.sendSyncMessageOnly(chatMessage);
if (isOurDevice) {
return this.sendSyncMessageOnly(chatMessage);
}
} }
if (conversation.isPrivate()) { if (conversation.isPrivate()) {
const [number] = recipients; return getMessageQueue().sendToPubKey(
const recipientPubKey = new PubKey(number); PubKey.cast(conversation.id),
chatMessage
);
}
return getMessageQueue().sendToPubKey(recipientPubKey, chatMessage); // Here, the convo is neither an open group, a private convo or ourself. It can only be a medium group.
// For a medium group, retry send only means trigger a send again to all recipients
// as they are all polling from the same group swarm pubkey
if (!conversation.isMediumGroup()) {
throw new Error(
'We should only end up with a medium group here. Anything else is an error'
);
} }
// TODO should we handle medium groups message here too?
// Not sure there is the concept of retrySend for those
const closedGroupChatMessage = new ClosedGroupChatMessage({ const closedGroupChatMessage = new ClosedGroupChatMessage({
identifier: this.id, identifier: this.id,
chatMessage, chatMessage,
groupId: this.get('conversationId'), groupId: this.get('conversationId'),
}); });
// Because this is a partial group send, we send the message with the groupId field set, but individually
// to each recipient listed
return Promise.all(
recipients.map(async r => {
const recipientPubKey = new PubKey(r);
return getMessageQueue().sendToPubKey(
recipientPubKey,
closedGroupChatMessage
);
})
);
} catch (e) {
await this.saveErrors(e);
return null;
}
}
// Called when the user ran into an error with a specific user, wants to send to them
public async resend(number: string) {
const error = this.removeOutgoingErrors(number);
if (!error) {
window.log.warn('resend: requested number was not present in errors');
return null;
}
try { return getMessageQueue().sendToGroup(closedGroupChatMessage);
const { body, attachments, preview, quote } = await this.uploadData();
const chatMessage = new ChatMessage({
identifier: this.id,
body,
timestamp: this.get('sent_at') || Date.now(),
expireTimer: this.get('expireTimer'),
attachments,
preview,
quote,
});
// Special-case the self-send case - we send only a sync message
if (UserUtils.isUsFromCache(number)) {
return this.sendSyncMessageOnly(chatMessage);
}
const conversation = this.getConversation();
const recipientPubKey = new PubKey(number);
if (conversation.isPrivate()) {
return getMessageQueue().sendToPubKey(recipientPubKey, chatMessage);
}
const closedGroupChatMessage = new ClosedGroupChatMessage({
chatMessage,
groupId: this.get('conversationId'),
});
// resend tries to send the message to that specific user only in the context of a closed group
return getMessageQueue().sendToPubKey(
recipientPubKey,
closedGroupChatMessage
);
} catch (e) { } catch (e) {
await this.saveErrors(e); await this.saveErrors(e);
return null; return null;
@ -1085,9 +1036,7 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
await this.commit(); await this.commit();
this.getConversation().updateLastMessage(); this.getConversation()?.updateLastMessage();
this.trigger('sent', this);
} }
public async handleMessageSentFailure(sentMessage: any, error: any) { public async handleMessageSentFailure(sentMessage: any, error: any) {
@ -1113,8 +1062,7 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
}); });
await this.commit(); await this.commit();
this.getConversation().updateLastMessage(); this.getConversation()?.updateLastMessage();
this.trigger('done');
} }
public getConversation() { public getConversation() {

@ -13,6 +13,8 @@ export type MessageDeliveryStatus =
| 'error'; | 'error';
export interface MessageAttributes { export interface MessageAttributes {
// the id of the message
// this can have several uses:
id: string; id: string;
source: string; source: string;
quote?: any; quote?: any;

@ -51,7 +51,7 @@ export class ConversationController {
); );
} }
// Needed for some model setup which happens during the initial fetch() call below // Needed for some model setup which happens during the initial fetch() call below
public getUnsafe(id: string) { public getUnsafe(id: string): ConversationModel | undefined {
return this.conversations.get(id); return this.conversations.get(id);
} }

@ -194,7 +194,7 @@ export async function addUpdateMessage(
const unread = type === 'incoming'; const unread = type === 'incoming';
const message = await convo.addMessage({ const message = await convo.addSingleMessage({
conversationId: convo.get('id'), conversationId: convo.get('id'),
type, type,
sent_at: now, sent_at: now,
@ -340,7 +340,7 @@ export async function leaveClosedGroup(groupId: string) {
convo.set({ groupAdmins: admins }); convo.set({ groupAdmins: admins });
await convo.commit(); await convo.commit();
const dbMessage = await convo.addMessage({ const dbMessage = await convo.addSingleMessage({
group_update: { left: 'You' }, group_update: { left: 'You' },
conversationId: groupId, conversationId: groupId,
type: 'outgoing', type: 'outgoing',

@ -1,4 +1,3 @@
export * from './ClosedGroupChatMessage';
export * from './ClosedGroupEncryptionPairMessage'; export * from './ClosedGroupEncryptionPairMessage';
export * from './ClosedGroupNewMessage'; export * from './ClosedGroupNewMessage';
export * from './ClosedGroupAddedMembersMessage'; export * from './ClosedGroupAddedMembersMessage';

@ -56,7 +56,6 @@ export interface ConversationType {
index?: number; index?: number;
activeAt?: number; activeAt?: number;
timestamp: number;
lastMessage?: { lastMessage?: {
status: 'error' | 'sending' | 'sent' | 'delivered' | 'read'; status: 'error' | 'sending' | 'sent' | 'delivered' | 'read';
text: string; text: string;
@ -443,15 +442,26 @@ function sortMessages(
isPublic: boolean isPublic: boolean
): Array<MessageTypeInConvo> { ): Array<MessageTypeInConvo> {
// we order by serverTimestamp for public convos // we order by serverTimestamp for public convos
// be sure to update the sorting order to fetch messages from the DB too at getMessagesByConversation
if (isPublic) { if (isPublic) {
return messages.sort( return messages.sort(
(a: any, b: any) => (a: any, b: any) =>
b.attributes.serverTimestamp - a.attributes.serverTimestamp b.attributes.serverTimestamp - a.attributes.serverTimestamp
); );
} }
return messages.sort( if (messages.some(n => !n.attributes.sent_at && !n.attributes.received_at)) {
(a: any, b: any) => b.attributes.timestamp - a.attributes.timestamp throw new Error('Found some messages without any timestamp set');
}
// for non public convos, we order by sent_at or received_at timestamp.
// we assume that a message has either a sent_at or a received_at field set.
const messagesSorted = messages.sort(
(a: any, b: any) =>
(b.attributes.sent_at || b.attributes.received_at) -
(a.attributes.sent_at || a.attributes.received_at)
); );
return messagesSorted;
} }
function handleMessageAdded( function handleMessageAdded(
@ -488,12 +498,13 @@ function handleMessageChanged(
state: ConversationsStateType, state: ConversationsStateType,
action: MessageChangedActionType action: MessageChangedActionType
) { ) {
const { payload } = action;
const messageInStoreIndex = state?.messages?.findIndex( const messageInStoreIndex = state?.messages?.findIndex(
m => m.id === action.payload.id m => m.id === payload.id
); );
if (messageInStoreIndex >= 0) { if (messageInStoreIndex >= 0) {
const changedMessage = _.pick( const changedMessage = _.pick(
action.payload as any, payload as any,
toPickFromMessageModel toPickFromMessageModel
) as MessageTypeInConvo; ) as MessageTypeInConvo;
// we cannot edit the array directly, so slice the first part, insert our edited message, and slice the second part // we cannot edit the array directly, so slice the first part, insert our edited message, and slice the second part
@ -503,7 +514,10 @@ function handleMessageChanged(
...state.messages.slice(messageInStoreIndex + 1), ...state.messages.slice(messageInStoreIndex + 1),
]; ];
const convo = state.conversationLookup[payload.get('conversationId')];
const isPublic = convo?.isPublic || false;
// reorder the messages depending on the timestamp (we might have an updated serverTimestamp now) // reorder the messages depending on the timestamp (we might have an updated serverTimestamp now)
const sortedMessage = sortMessages(editedMessages, isPublic);
const updatedWithFirstMessageOfSeries = updateFirstMessageOfSeries( const updatedWithFirstMessageOfSeries = updateFirstMessageOfSeries(
editedMessages editedMessages
); );

@ -142,7 +142,7 @@ export const _getLeftPaneLists = (
} }
// Show loading icon while fetching messages // Show loading icon while fetching messages
if (conversation.isPublic && !conversation.timestamp) { if (conversation.isPublic && !conversation.activeAt) {
conversation.lastMessage = { conversation.lastMessage = {
status: 'sending', status: 'sending',
text: '', text: '',

@ -13,9 +13,8 @@ describe('state/selectors/conversations', () => {
const data: ConversationLookupType = { const data: ConversationLookupType = {
id1: { id1: {
id: 'id1', id: 'id1',
activeAt: Date.now(), activeAt: 0,
name: 'No timestamp', name: 'No timestamp',
timestamp: 0,
phoneNumber: 'notused', phoneNumber: 'notused',
type: 'direct', type: 'direct',
@ -30,9 +29,8 @@ describe('state/selectors/conversations', () => {
}, },
id2: { id2: {
id: 'id2', id: 'id2',
activeAt: Date.now(), activeAt: 20,
name: 'B', name: 'B',
timestamp: 20,
phoneNumber: 'notused', phoneNumber: 'notused',
type: 'direct', type: 'direct',
@ -47,9 +45,8 @@ describe('state/selectors/conversations', () => {
}, },
id3: { id3: {
id: 'id3', id: 'id3',
activeAt: Date.now(), activeAt: 20,
name: 'C', name: 'C',
timestamp: 20,
phoneNumber: 'notused', phoneNumber: 'notused',
type: 'direct', type: 'direct',
@ -64,9 +61,8 @@ describe('state/selectors/conversations', () => {
}, },
id4: { id4: {
id: 'id4', id: 'id4',
activeAt: Date.now(), activeAt: 20,
name: 'Á', name: 'Á',
timestamp: 20,
phoneNumber: 'notused', phoneNumber: 'notused',
type: 'direct', type: 'direct',
isMe: false, isMe: false,
@ -80,9 +76,8 @@ describe('state/selectors/conversations', () => {
}, },
id5: { id5: {
id: 'id5', id: 'id5',
activeAt: Date.now(), activeAt: 30,
name: 'First!', name: 'First!',
timestamp: 30,
phoneNumber: 'notused', phoneNumber: 'notused',
type: 'direct', type: 'direct',
isMe: false, isMe: false,

@ -5,7 +5,7 @@ import {
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
import { OpenGroup } from '../../../session/types'; import { OpenGroup } from '../../../session/types';
import { generateFakePubKey, generateFakePubKeys } from './pubkey'; import { generateFakePubKey, generateFakePubKeys } from './pubkey';
import { ClosedGroupChatMessage } from '../../../session/messages/outgoing/content/data/group'; import { ClosedGroupChatMessage } from '../../../session/messages/outgoing/content/data/group/ClosedGroupChatMessage';
import { ConversationAttributes } from '../../../models/conversation'; import { ConversationAttributes } from '../../../models/conversation';
export function generateChatMessage(identifier?: string): ChatMessage { export function generateChatMessage(identifier?: string): ChatMessage {

Loading…
Cancel
Save