From 18f15615d4baac0624af49d0be0b6692923bc78d Mon Sep 17 00:00:00 2001 From: Brice-W Date: Tue, 20 Jul 2021 17:09:17 +1000 Subject: [PATCH 01/10] don't render messages that are already expired --- ts/components/session/conversation/SessionMessagesList.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ts/components/session/conversation/SessionMessagesList.tsx b/ts/components/session/conversation/SessionMessagesList.tsx index 922a40d31..3d3f460fd 100644 --- a/ts/components/session/conversation/SessionMessagesList.tsx +++ b/ts/components/session/conversation/SessionMessagesList.tsx @@ -318,6 +318,10 @@ export class SessionMessagesList extends React.Component { multiSelectMode: boolean, message: MessageModel ) { + if (messageProps.expirationTimestamp != null && messageProps.expirationTimestamp < Date.now()) { + return; + } + const selected = !!messageProps?.id && this.props.selectedMessages.includes(messageProps.id); messageProps.selected = selected; From e5615357f7cd28c772f4fbf5209e8b103aa519f2 Mon Sep 17 00:00:00 2001 From: Brice-W Date: Thu, 22 Jul 2021 14:58:00 +1000 Subject: [PATCH 02/10] call setToExpire when expire timer is started to set expire_at property of message --- ts/components/session/conversation/SessionMessagesList.tsx | 4 ---- ts/models/message.ts | 5 +++-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/ts/components/session/conversation/SessionMessagesList.tsx b/ts/components/session/conversation/SessionMessagesList.tsx index 3d3f460fd..922a40d31 100644 --- a/ts/components/session/conversation/SessionMessagesList.tsx +++ b/ts/components/session/conversation/SessionMessagesList.tsx @@ -318,10 +318,6 @@ export class SessionMessagesList extends React.Component { multiSelectMode: boolean, message: MessageModel ) { - if (messageProps.expirationTimestamp != null && messageProps.expirationTimestamp < Date.now()) { - return; - } - const selected = !!messageProps?.id && this.props.selectedMessages.includes(messageProps.id); messageProps.selected = selected; diff --git a/ts/models/message.ts b/ts/models/message.ts index ec58ecf97..c755b8664 100644 --- a/ts/models/message.ts +++ b/ts/models/message.ts @@ -1058,6 +1058,7 @@ export class MessageModel extends Backbone.Model { if (this.get('expireTimer') && !this.get('expirationStartTimestamp')) { const expirationStartTimestamp = Math.min(Date.now(), readAt || Date.now()); this.set({ expirationStartTimestamp }); + this.setToExpire(false); } window.Whisper.Notifications.remove( @@ -1092,7 +1093,7 @@ export class MessageModel extends Backbone.Model { return msFromNow; } - public async setToExpire(force = false) { + public async setToExpire(commit = true, force = false) { if (this.isExpiring() && (force || !this.get('expires_at'))) { const start = this.get('expirationStartTimestamp'); const delta = this.get('expireTimer') * 1000; @@ -1103,7 +1104,7 @@ export class MessageModel extends Backbone.Model { this.set({ expires_at: expiresAt }); const id = this.get('id'); - if (id) { + if (id && commit) { await this.commit(); } From 6bbabce8e366ab3bf4b2722878cde726ab30bff0 Mon Sep 17 00:00:00 2001 From: Brice-W Date: Thu, 22 Jul 2021 15:21:52 +1000 Subject: [PATCH 03/10] fix async issue --- ts/models/conversation.ts | 2 +- ts/models/message.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ts/models/conversation.ts b/ts/models/conversation.ts index 7b879d0a4..10c826805 100644 --- a/ts/models/conversation.ts +++ b/ts/models/conversation.ts @@ -947,7 +947,7 @@ export class ConversationModel extends Backbone.Model { // Build the list of updated message models so we can mark them all as read on a single sqlite call for (const nowRead of oldUnreadNowRead) { - nowRead.markReadNoCommit(options.readAt); + await nowRead.markReadNoCommit(options.readAt); const errors = nowRead.get('errors'); read.push({ diff --git a/ts/models/message.ts b/ts/models/message.ts index c755b8664..f02675f3d 100644 --- a/ts/models/message.ts +++ b/ts/models/message.ts @@ -1045,20 +1045,20 @@ export class MessageModel extends Backbone.Model { } public async markRead(readAt: number) { - this.markReadNoCommit(readAt); + await this.markReadNoCommit(readAt); this.getConversation()?.markRead(this.attributes.received_at); await this.commit(); } - public markReadNoCommit(readAt: number) { + public async markReadNoCommit(readAt: number) { this.set({ unread: 0 }); if (this.get('expireTimer') && !this.get('expirationStartTimestamp')) { const expirationStartTimestamp = Math.min(Date.now(), readAt || Date.now()); this.set({ expirationStartTimestamp }); - this.setToExpire(false); + await this.setToExpire(false); } window.Whisper.Notifications.remove( From beca14982c99e40982fbf5c5cf00e8bc2447e89c Mon Sep 17 00:00:00 2001 From: Brice-W Date: Mon, 26 Jul 2021 10:43:12 +1000 Subject: [PATCH 04/10] manage expired nessage deletion in setExpired --- ts/components/conversation/Message.tsx | 16 +++++++++++++--- ts/data/data.ts | 17 ++++++++++++++--- ts/models/conversation.ts | 3 ++- ts/models/message.ts | 10 ++++++---- ts/models/messageType.ts | 7 +++++++ 5 files changed, 42 insertions(+), 11 deletions(-) diff --git a/ts/components/conversation/Message.tsx b/ts/components/conversation/Message.tsx index dda2867bc..ffdd51a5d 100644 --- a/ts/components/conversation/Message.tsx +++ b/ts/components/conversation/Message.tsx @@ -28,16 +28,17 @@ import { isFileDangerous } from '../../util/isFileDangerous'; import _ from 'lodash'; import { animation, contextMenu, Item, Menu } from 'react-contexify'; import uuid from 'uuid'; -import { InView } from 'react-intersection-observer'; import { withTheme } from 'styled-components'; import { MessageMetadata } from './message/MessageMetadata'; import { PubKey } from '../../session/types'; +import { getConversationController } from '../../session/conversations'; import { MessageRegularProps } from '../../models/messageType'; import { addSenderAsModerator, removeSenderFromModerator, } from '../../interactions/messageInteractions'; import { updateUserDetailsModal } from '../../state/ducks/modalDialog'; +import { actions as conversationActions } from '../../state/ducks/conversations'; import { MessageInteraction } from '../../interactions'; import autoBind from 'auto-bind'; import { AudioPlayerWithEncryptedFile } from './H5AudioPlayer'; @@ -104,7 +105,7 @@ class MessageInner extends React.PureComponent { public checkExpired() { const now = Date.now(); - const { isExpired, expirationTimestamp, expirationLength } = this.props; + const { isExpired, expirationTimestamp, expirationLength, convoId, id } = this.props; if (!expirationTimestamp || !expirationLength) { return; @@ -118,10 +119,19 @@ class MessageInner extends React.PureComponent { expiring: true, }); - const setExpired = () => { + const setExpired = async () => { this.setState({ expired: true, }); + await window.Signal.Data.removeMessage(id); + window.inboxStore?.dispatch( + conversationActions.messageExpired({ + conversationKey: convoId, + messageId: id, + }) + ); + const convo = getConversationController().get(convoId); + convo.updateLastMessage(); }; this.expiredTimeout = setTimeout(setExpired, EXPIRED_DELAY); } diff --git a/ts/data/data.ts b/ts/data/data.ts index 77ca3175a..3bffa8818 100644 --- a/ts/data/data.ts +++ b/ts/data/data.ts @@ -637,7 +637,7 @@ export async function saveMessages(arrayOfMessages: Array): P } export async function removeMessage(id: string): Promise { - const message = await getMessageById(id); + const message = await getMessageById(id, true); // Note: It's important to have a fully database-hydrated model to delete here because // it needs to delete all associated on-disk files along with the database delete. @@ -659,11 +659,17 @@ export async function getMessageIdsFromServerIds( return channels.getMessageIdsFromServerIds(serverIds, conversationId); } -export async function getMessageById(id: string): Promise { +export async function getMessageById( + id: string, + skipTimerInit: boolean = false +): Promise { const message = await channels.getMessageById(id); if (!message) { return null; } + if (skipTimerInit) { + message.skipTimerInit = skipTimerInit; + } return new MessageModel(message); } @@ -748,13 +754,18 @@ export async function getUnreadCountByConversation(conversationId: string): Prom export async function getMessagesByConversation( conversationId: string, - { limit = 100, receivedAt = Number.MAX_VALUE, type = '%' } + { limit = 100, receivedAt = Number.MAX_VALUE, type = '%', skipTimerInit = false } ): Promise { const messages = await channels.getMessagesByConversation(conversationId, { limit, receivedAt, type, }); + if (skipTimerInit) { + for (const message of messages) { + message.skipTimerInit = skipTimerInit; + } + } return new MessageCollection(messages); } diff --git a/ts/models/conversation.ts b/ts/models/conversation.ts index 10c826805..363b104d3 100644 --- a/ts/models/conversation.ts +++ b/ts/models/conversation.ts @@ -758,6 +758,7 @@ export class ConversationModel extends Backbone.Model { } const messages = await getMessagesByConversation(this.id, { limit: 1, + skipTimerInit: true, }); const lastMessageModel = messages.at(0); const lastMessageJSON = lastMessageModel ? lastMessageModel.toJSON() : null; @@ -947,7 +948,7 @@ export class ConversationModel extends Backbone.Model { // Build the list of updated message models so we can mark them all as read on a single sqlite call for (const nowRead of oldUnreadNowRead) { - await nowRead.markReadNoCommit(options.readAt); + nowRead.markReadNoCommit(options.readAt); const errors = nowRead.get('errors'); read.push({ diff --git a/ts/models/message.ts b/ts/models/message.ts index f02675f3d..41811e1d8 100644 --- a/ts/models/message.ts +++ b/ts/models/message.ts @@ -63,7 +63,9 @@ export class MessageModel extends Backbone.Model { } // this.on('expired', this.onExpired); - void this.setToExpire(); + if (!filledAttrs.skipTimerInit) { + void this.setToExpire(); + } autoBind(this); window.contextMenuShown = false; @@ -1045,20 +1047,20 @@ export class MessageModel extends Backbone.Model { } public async markRead(readAt: number) { - await this.markReadNoCommit(readAt); + this.markReadNoCommit(readAt); this.getConversation()?.markRead(this.attributes.received_at); await this.commit(); } - public async markReadNoCommit(readAt: number) { + public markReadNoCommit(readAt: number) { this.set({ unread: 0 }); if (this.get('expireTimer') && !this.get('expirationStartTimestamp')) { const expirationStartTimestamp = Math.min(Date.now(), readAt || Date.now()); this.set({ expirationStartTimestamp }); - await this.setToExpire(false); + //await this.setToExpire(false); } window.Whisper.Notifications.remove( diff --git a/ts/models/messageType.ts b/ts/models/messageType.ts index cf945bfaa..fbf1b6e41 100644 --- a/ts/models/messageType.ts +++ b/ts/models/messageType.ts @@ -101,6 +101,12 @@ export interface MessageAttributes { * We display a small message just below the message referenced */ dataExtractionNotification?: DataExtractionNotificationMsg; + + /** + * This is used to choose whether to initialize the timer or not in the MessageModel object. + * If false or undefined, timer will be in itialized. + */ + skipTimerInit?: boolean; } export interface DataExtractionNotificationMsg { @@ -165,6 +171,7 @@ export interface MessageAttributesOptionals { sync?: boolean; snippet?: any; direction?: any; + skipTimerInit?: boolean; } /** From 72868c10e5ab4b7a15727667906fb32210115d9e Mon Sep 17 00:00:00 2001 From: Brice-W Date: Mon, 26 Jul 2021 10:47:10 +1000 Subject: [PATCH 05/10] clean --- ts/models/message.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/ts/models/message.ts b/ts/models/message.ts index 41811e1d8..493a62bae 100644 --- a/ts/models/message.ts +++ b/ts/models/message.ts @@ -1060,7 +1060,6 @@ export class MessageModel extends Backbone.Model { if (this.get('expireTimer') && !this.get('expirationStartTimestamp')) { const expirationStartTimestamp = Math.min(Date.now(), readAt || Date.now()); this.set({ expirationStartTimestamp }); - //await this.setToExpire(false); } window.Whisper.Notifications.remove( From d7eafb7e152e107a52ae2713af2a9eedbaf5dba6 Mon Sep 17 00:00:00 2001 From: Brice-W Date: Mon, 26 Jul 2021 10:49:35 +1000 Subject: [PATCH 06/10] rollback changes --- ts/models/message.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ts/models/message.ts b/ts/models/message.ts index 493a62bae..893cd42cb 100644 --- a/ts/models/message.ts +++ b/ts/models/message.ts @@ -1094,7 +1094,7 @@ export class MessageModel extends Backbone.Model { return msFromNow; } - public async setToExpire(commit = true, force = false) { + public async setToExpire(force = false) { if (this.isExpiring() && (force || !this.get('expires_at'))) { const start = this.get('expirationStartTimestamp'); const delta = this.get('expireTimer') * 1000; @@ -1105,7 +1105,7 @@ export class MessageModel extends Backbone.Model { this.set({ expires_at: expiresAt }); const id = this.get('id'); - if (id && commit) { + if (id) { await this.commit(); } From 751d0b9348365680752faeefbe46ba84d7fdf391 Mon Sep 17 00:00:00 2001 From: Brice-W Date: Mon, 26 Jul 2021 11:12:42 +1000 Subject: [PATCH 07/10] clearTimout call added to checkExpired to avoid multiple calls to setExpired --- ts/components/conversation/Message.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ts/components/conversation/Message.tsx b/ts/components/conversation/Message.tsx index ffdd51a5d..f6b606939 100644 --- a/ts/components/conversation/Message.tsx +++ b/ts/components/conversation/Message.tsx @@ -133,6 +133,9 @@ class MessageInner extends React.PureComponent { const convo = getConversationController().get(convoId); convo.updateLastMessage(); }; + // as 'checkExpired' is potentially called more than once (componentDidUpdate & componentDidMount), + // we need to clear the timeout call to 'setExpired' first to avoid multiple calls to 'setExpired'. + clearTimeout(this.expiredTimeout); this.expiredTimeout = setTimeout(setExpired, EXPIRED_DELAY); } } From 54a2eef700df6d27e753507a6b49639d2c750c16 Mon Sep 17 00:00:00 2001 From: Brice-W Date: Mon, 26 Jul 2021 15:11:20 +1000 Subject: [PATCH 08/10] change the way skipTimerInit is defined in MessageModel --- ts/interactions/conversationInteractions.ts | 2 +- ts/models/message.ts | 4 ++-- ts/models/messageType.ts | 7 ------- 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/ts/interactions/conversationInteractions.ts b/ts/interactions/conversationInteractions.ts index e9082230d..5b08d275f 100644 --- a/ts/interactions/conversationInteractions.ts +++ b/ts/interactions/conversationInteractions.ts @@ -439,7 +439,7 @@ export async function deleteMessagesById( askUserForConfirmation: boolean ) { const conversationModel = getConversationController().getOrThrow(conversationId); - const selectedMessages = _.compact(await Promise.all(messageIds.map(getMessageById))); + const selectedMessages = _.compact(await Promise.all(messageIds.map(m => getMessageById(m)))); const moreThanOne = selectedMessages.length > 1; diff --git a/ts/models/message.ts b/ts/models/message.ts index 4e0d11964..405fc0180 100644 --- a/ts/models/message.ts +++ b/ts/models/message.ts @@ -57,7 +57,7 @@ import { perfEnd, perfStart } from '../session/utils/Performance'; import { AttachmentTypeWithPath } from '../types/Attachment'; export class MessageModel extends Backbone.Model { - constructor(attributes: MessageAttributesOptionals) { + constructor(attributes: MessageAttributesOptionals & { skipTimerInit?: boolean }) { const filledAttrs = fillMessageAttributesWithDefaults(attributes); super(filledAttrs); @@ -76,7 +76,7 @@ export class MessageModel extends Backbone.Model { } // this.on('expired', this.onExpired); - if (!filledAttrs.skipTimerInit) { + if (!attributes.skipTimerInit) { void this.setToExpire(); } autoBind(this); diff --git a/ts/models/messageType.ts b/ts/models/messageType.ts index cfa14270e..353215118 100644 --- a/ts/models/messageType.ts +++ b/ts/models/messageType.ts @@ -102,12 +102,6 @@ export interface MessageAttributes { * We display a small message just below the message referenced */ dataExtractionNotification?: DataExtractionNotificationMsg; - - /** - * This is used to choose whether to initialize the timer or not in the MessageModel object. - * If false or undefined, timer will be in itialized. - */ - skipTimerInit?: boolean; } export interface DataExtractionNotificationMsg { @@ -173,7 +167,6 @@ export interface MessageAttributesOptionals { sync?: boolean; snippet?: any; direction?: any; - skipTimerInit?: boolean; } /** From e31b219bd4600c8c9e38429f9834f9e5175cced4 Mon Sep 17 00:00:00 2001 From: Brice-W Date: Mon, 26 Jul 2021 15:46:36 +1000 Subject: [PATCH 09/10] fix --- ts/interactions/conversationInteractions.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ts/interactions/conversationInteractions.ts b/ts/interactions/conversationInteractions.ts index 5b08d275f..6a11be9e9 100644 --- a/ts/interactions/conversationInteractions.ts +++ b/ts/interactions/conversationInteractions.ts @@ -439,7 +439,9 @@ export async function deleteMessagesById( askUserForConfirmation: boolean ) { const conversationModel = getConversationController().getOrThrow(conversationId); - const selectedMessages = _.compact(await Promise.all(messageIds.map(m => getMessageById(m)))); + const selectedMessages = _.compact( + await Promise.all(messageIds.map(m => getMessageById(m, false))) + ); const moreThanOne = selectedMessages.length > 1; From 9c61defc0b618d0c8946af17050d389a5bdfedd1 Mon Sep 17 00:00:00 2001 From: Brice-W Date: Tue, 27 Jul 2021 10:33:35 +1000 Subject: [PATCH 10/10] small update --- ts/components/conversation/Message.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts/components/conversation/Message.tsx b/ts/components/conversation/Message.tsx index 4fc803cf8..92df95024 100644 --- a/ts/components/conversation/Message.tsx +++ b/ts/components/conversation/Message.tsx @@ -194,7 +194,7 @@ class MessageInner extends React.PureComponent { }; // as 'checkExpired' is potentially called more than once (componentDidUpdate & componentDidMount), // we need to clear the timeout call to 'setExpired' first to avoid multiple calls to 'setExpired'. - clearTimeout(this.expiredTimeout); + global.clearTimeout(this.expiredTimeout); this.expiredTimeout = setTimeout(setExpired, EXPIRED_DELAY); } }