From 97ecc9e521d90b98358e85549ae6eb00a8c1432b Mon Sep 17 00:00:00 2001 From: William Grant Date: Mon, 3 Apr 2023 14:09:05 +0200 Subject: [PATCH] feat: fixed double expiration update message issue started creating an inheritable expiration message class, improved start triggering logic --- .../message-item/GroupUpdateMessage.tsx | 21 +++++-- ts/interactions/conversationInteractions.ts | 1 + ts/models/conversation.ts | 62 +++++++++++-------- ts/models/message.ts | 29 ++++----- ts/receiver/contentMessage.ts | 35 +++++------ ts/receiver/dataMessage.ts | 31 +++++++--- ts/receiver/queuedJob.ts | 60 ++++++++---------- .../messages/outgoing/ContentMessage.ts | 1 + ts/session/messages/outgoing/DataMessage.ts | 5 +- .../messages/outgoing/ExpirableMessage.ts | 34 ++++++++++ .../ExpirationTimerUpdateMessage.ts | 31 +++++----- .../outgoing/visibleMessage/VisibleMessage.ts | 28 ++++----- ts/session/utils/syncUtils.ts | 1 + ts/state/ducks/conversations.ts | 4 +- 14 files changed, 201 insertions(+), 142 deletions(-) create mode 100644 ts/session/messages/outgoing/ExpirableMessage.ts diff --git a/ts/components/conversation/message/message-item/GroupUpdateMessage.tsx b/ts/components/conversation/message/message-item/GroupUpdateMessage.tsx index 96afe7bc3..ff6e7f99c 100644 --- a/ts/components/conversation/message/message-item/GroupUpdateMessage.tsx +++ b/ts/components/conversation/message/message-item/GroupUpdateMessage.tsx @@ -5,7 +5,7 @@ import { PropsForGroupUpdateType, } from '../../../../state/ducks/conversations'; import { NotificationBubble } from './notification-bubble/NotificationBubble'; -import { ReadableMessage } from './ReadableMessage'; +import { ExpirableReadableMessage } from './ExpirableReadableMessage'; import { arrayContainsUsOnly } from '../../../../models/message'; import { useConversationsUsernameWithQuoteOrFullPubkey } from '../../../../hooks/useParamSelector'; @@ -71,16 +71,29 @@ const ChangeItem = (change: PropsForGroupUpdateType): string => { }; export const GroupUpdateMessage = (props: PropsForGroupUpdate) => { - const { change, messageId, receivedAt, isUnread } = props; + const { + change, + messageId, + receivedAt, + isUnread, + direction, + expirationLength, + expirationTimestamp, + isExpired, + } = props; return ( - - + ); }; diff --git a/ts/interactions/conversationInteractions.ts b/ts/interactions/conversationInteractions.ts index c9b2f21e5..9ed727b46 100644 --- a/ts/interactions/conversationInteractions.ts +++ b/ts/interactions/conversationInteractions.ts @@ -362,6 +362,7 @@ export async function setDisappearingMessagesByConvoId( if (!expirationType || expirationType === 'off' || !seconds || seconds <= 0) { await conversation.updateExpireTimer({ providedExpirationType: 'off', + providedExpireTimer: 0, providedChangeTimestamp, }); } else { diff --git a/ts/models/conversation.ts b/ts/models/conversation.ts index 393bd2867..2665e697f 100644 --- a/ts/models/conversation.ts +++ b/ts/models/conversation.ts @@ -1036,17 +1036,20 @@ export class ConversationModel extends Backbone.Model { receivedAt, // is set if it comes from outside fromSync, shouldCommit = true, + existingMessage, }: { providedExpirationType: DisappearingMessageConversationType; providedExpireTimer?: number; - providedChangeTimestamp?: number; + providedChangeTimestamp: number; providedSource?: string; receivedAt?: number; // is set if it comes from outside fromSync?: boolean; shouldCommit?: boolean; + existingMessage?: MessageModel; }): Promise { let expirationType = providedExpirationType; let expireTimer = providedExpireTimer; + const lastDisappearingMessageChangeTimestamp = providedChangeTimestamp; let source = providedSource; defaults({ fromSync }, { fromSync: false }); @@ -1057,12 +1060,19 @@ export class ConversationModel extends Backbone.Model { } // TODO does this actually work? + if ( + this.get('lastDisappearingMessageChangeTimestamp') > lastDisappearingMessageChangeTimestamp + ) { + window.log.info('WIP: updateExpireTimer() This is an outdated disappearing message setting'); + return; + } + if ( isEqual(expirationType, this.get('expirationType')) && isEqual(expireTimer, this.get('expireTimer')) ) { window.log.info( - 'WIP: Dropping ExpireTimerUpdate message as we already have the same one set.' + 'WIP:updateExpireTimer() Dropping ExpireTimerUpdate message as we already have the same one set.' ); return; } @@ -1077,17 +1087,17 @@ export class ConversationModel extends Backbone.Model { this.set({ expirationType, expireTimer, - lastDisappearingMessageChangeTimestamp: providedChangeTimestamp || undefined, + lastDisappearingMessageChangeTimestamp, }); - window?.log?.info('WIP: Updated conversation disappearing messages setting', { + window?.log?.info('WIP: Updating conversation disappearing messages setting', { id: this.idForLogging(), expirationType, expireTimer, + lastDisappearingMessageChangeTimestamp, source, }); - const lastDisappearingMessageChangeTimestamp = providedChangeTimestamp || 0; const commonAttributes = { flags: SignalService.DataMessage.Flags.EXPIRATION_TIMER_UPDATE, expirationTimerUpdate: { @@ -1097,28 +1107,30 @@ export class ConversationModel extends Backbone.Model { source, fromSync, }, - expirationType, - expireTimer, + expirationType: expirationType !== 'off' ? expirationType : undefined, + expireTimer: expirationType !== 'off' ? expireTimer : undefined, }; - let message: MessageModel | undefined; + let message: MessageModel | undefined = existingMessage || undefined; - if (isOutgoing) { - message = await this.addSingleOutgoingMessage({ - ...commonAttributes, - sent_at: timestamp, - }); - } else { - // TODO do we still want to handle expiration in incoming messages? - message = await this.addSingleIncomingMessage({ - ...commonAttributes, - // 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. - unread: 1, - source, - sent_at: timestamp, - received_at: timestamp, - }); + if (!message) { + if (isOutgoing) { + message = await this.addSingleOutgoingMessage({ + ...commonAttributes, + sent_at: timestamp, + }); + } else { + // TODO do we still want to handle expiration in incoming messages? + message = await this.addSingleIncomingMessage({ + ...commonAttributes, + // 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. + unread: 1, + source, + sent_at: timestamp, + received_at: timestamp, + }); + } } if (this.isActive()) { @@ -1144,6 +1156,7 @@ export class ConversationModel extends Backbone.Model { if (this.isMe()) { // TODO Check that the args are correct + // This might be happening too late in the message pipeline. Maybe should be moved to handleExpirationTimerUpdateNoCommit() if (expireUpdate.expirationType === 'deleteAfterRead') { window.log.info('WIP: Note to Self messages cannot be delete after read!'); return; @@ -1159,7 +1172,6 @@ export class ConversationModel extends Backbone.Model { const pubkey = new PubKey(this.get('id')); await getMessageQueue().sendToPubKey(pubkey, expirationTimerMessage); } else { - // TODO Check that the args are correct // Cannot be an open group window?.log?.warn('TODO: Expiration update for closed groups are to be updated'); const expireUpdateForGroup = { diff --git a/ts/models/message.ts b/ts/models/message.ts index 70b9e89ed..b59a18957 100644 --- a/ts/models/message.ts +++ b/ts/models/message.ts @@ -250,18 +250,12 @@ export class MessageModel extends Backbone.Model { return window.i18n('mediaMessage'); } if (this.isExpirationTimerUpdate()) { - // Backwards compatibility for Disappearing Messages in old clients + // TODO Backwards compatibility for Disappearing Messages in old clients + // TODO What does this comment refer to mean? const expireTimerUpdate = this.get('expirationTimerUpdate'); - const expirationType = this.get('expirationType'); - const expireTimer = this.get('expireTimer'); - if ( - !expireTimerUpdate || - expireTimerUpdate.expirationType === 'off' || - !expireTimerUpdate.expireTimer || - expirationType === 'off' || - !expireTimer || - expireTimer === 0 - ) { + const expirationType = expireTimerUpdate?.expirationType; + const expireTimer = expireTimerUpdate?.expireTimer; + if (!expireTimerUpdate || expirationType === 'off' || !expireTimer || expireTimer === 0) { return window.i18n('disappearingMessagesDisabled'); } @@ -417,6 +411,7 @@ export class MessageModel extends Backbone.Model { messageId: this.id, isUnread: this.isUnread(), receivedAt: this.get('received_at'), + ...this.getPropsForExpiringMessage(), }; if (groupUpdate.joined?.length) { @@ -1063,20 +1058,20 @@ export class MessageModel extends Backbone.Model { } public async sendSyncMessageOnly(dataMessage: DataMessage) { + const contentMessage = dataMessage.contentProto(); const now = Date.now(); + this.set({ sent_to: [UserUtils.getOurPubKeyStrFromCache()], sent: true, - // NOTE if disappearing message is deleteAfterRead then we don't use this - expirationStartTimestamp: now, }); - const contentMessage = dataMessage.contentProto(); let expireUpdate = null; + const expirationType = dataMessage.getDisappearingMessageType(); - if (contentMessage.expirationType && contentMessage.expirationTimer) { + if (expirationType && contentMessage.expirationTimer) { expireUpdate = { - expirationType: contentMessage.expirationType, + expirationType, expireTimer: contentMessage.expirationTimer, lastDisappearingMessageChangeTimestamp: contentMessage.lastDisappearingMessageChangeTimestamp, @@ -1091,6 +1086,7 @@ export class MessageModel extends Backbone.Model { public async sendSyncMessage( data: DataMessage | SignalService.DataMessage, sentTimestamp: number, + // TODO add proper types expireUpdate?: any ) { if (this.get('synced') || this.get('sentSync')) { @@ -1174,6 +1170,7 @@ export class MessageModel extends Backbone.Model { await this.commit(); // the line below makes sure that getNextExpiringMessage will find this message as expiring. // getNextExpiringMessage is used on app start to clean already expired messages which should have been removed already, but are not + await this.setToExpire(); const convo = this.getConversation(); diff --git a/ts/receiver/contentMessage.ts b/ts/receiver/contentMessage.ts index df4d62c23..7577e29cd 100644 --- a/ts/receiver/contentMessage.ts +++ b/ts/receiver/contentMessage.ts @@ -3,7 +3,7 @@ import { handleSwarmDataMessage } from './dataMessage'; import { removeFromCache, updateCache } from './cache'; import { SignalService } from '../protobuf'; -import { compact, flatten, identity, isEmpty, pickBy, toNumber } from 'lodash'; +import { compact, flatten, identity, isEmpty, isEqual, pickBy, toNumber } from 'lodash'; import { KeyPrefixType, PubKey } from '../session/types'; import { BlockedNumberController } from '../util/blockedNumberController'; @@ -406,31 +406,28 @@ export async function innerHandleSwarmContentMessage( perfStart(`handleSwarmDataMessage-${envelope.id}`); - let expireUpdate = null; - - const expirationType = - DisappearingMessageConversationSetting[content.expirationType] || 'off'; - let expireTimer = content.expirationTimer || 0; + const expireUpdate = { + expirationType: DisappearingMessageConversationSetting[content.expirationType] || 'off', + // TODO rename to expirationTimer? + expireTimer: content.expirationTimer || 0, + // This is used for the expirationTimerUpdate + lastDisappearingMessageChangeTimestamp: content.lastDisappearingMessageChangeTimestamp + ? Number(content.lastDisappearingMessageChangeTimestamp) + : undefined, + }; // TODO in the future we will remove the dataMessage expireTimer // Backwards compatibility for Disappearing Messages in old clients - if (dataMessage.expireTimer) { + if ( + expireUpdate.expireTimer > 0 && + dataMessage.expireTimer && + !isEqual(expireUpdate.expireTimer, dataMessage.expireTimer) + ) { // TODO Trigger banner in UI? - expireTimer = dataMessage.expireTimer; + expireUpdate.expireTimer = dataMessage.expireTimer; window.log.info('WIP: Received outdated disappearing message data message', content); } - // NOTE In the protobuf this is a long - const lastDisappearingMessageChangeTimestamp = - Number(content.lastDisappearingMessageChangeTimestamp) || null; - - expireUpdate = { - expirationType, - // TODO rename to expirationTimer? - expireTimer, - lastDisappearingMessageChangeTimestamp, - }; - await handleSwarmDataMessage( envelope, sentAtTimestamp, diff --git a/ts/receiver/dataMessage.ts b/ts/receiver/dataMessage.ts index 554b8b759..92cac808f 100644 --- a/ts/receiver/dataMessage.ts +++ b/ts/receiver/dataMessage.ts @@ -154,7 +154,8 @@ export async function handleSwarmDataMessage( rawDataMessage: SignalService.DataMessage, messageHash: string, senderConversationModel: ConversationModel, - expireUpdate: any + // TODO add proper types + expireUpdate?: any ): Promise { window.log.info('handleSwarmDataMessage'); @@ -245,6 +246,25 @@ export async function handleSwarmDataMessage( if (isSyncedMessage) { // TODO handle sync messages separately window.log.info('WIP: Sync Message dropping'); + } else { + const { expirationType, expireTimer, lastDisappearingMessageChangeTimestamp } = expireUpdate; + + msgModel.set({ + expirationType, + expireTimer, + }); + + // This message is conversation setting change message + if (expireUpdate.lastDisappearingMessageChangeTimestamp) { + msgModel.set({ + expirationTimerUpdate: { + expirationType, + expireTimer, + lastDisappearingMessageChangeTimestamp, + source: msgModel.get('source'), + }, + }); + } } await handleSwarmMessage( @@ -253,8 +273,7 @@ export async function handleSwarmDataMessage( sentAtTimestamp, cleanDataMessage, convoToAddMessageTo, - () => removeFromCache(envelope), - isSyncedMessage ? expireUpdate : null + () => removeFromCache(envelope) ); } @@ -301,8 +320,7 @@ async function handleSwarmMessage( sentAt: number, rawDataMessage: SignalService.DataMessage, convoToAddMessageTo: ConversationModel, - confirm: () => void, - expireUpdate?: any + confirm: () => void ): Promise { if (!rawDataMessage || !msgModel) { window?.log?.warn('Invalid data passed to handleSwarmMessage.'); @@ -350,8 +368,7 @@ async function handleSwarmMessage( toRegularMessage(rawDataMessage), confirm, msgModel.get('source'), - messageHash, - expireUpdate + messageHash ); }); } diff --git a/ts/receiver/queuedJob.ts b/ts/receiver/queuedJob.ts index aa64de826..9b83d37e8 100644 --- a/ts/receiver/queuedJob.ts +++ b/ts/receiver/queuedJob.ts @@ -1,7 +1,7 @@ import { queueAttachmentDownloads } from './attachments'; import { Quote } from './types'; -import _, { isEmpty, isEqual } from 'lodash'; +import _, { isEqual } from 'lodash'; import { getConversationController } from '../session/conversations'; import { ConversationModel } from '../models/conversation'; import { MessageModel, sliceQuoteText } from '../models/message'; @@ -308,16 +308,9 @@ async function handleExpirationTimerUpdateNoCommit( message: MessageModel, source: string, expirationType: DisappearingMessageConversationType, - expireTimer: number + expireTimer: number, + lastDisappearingMessageChangeTimestamp: number ) { - const providedChangeTimestamp = getNowWithNetworkOffset(); - - // TODO Not entirely sure that this works - if (conversation.get('lastDisappearingMessageChangeTimestamp') > providedChangeTimestamp) { - window.log.info('WIP: This is an outdated disappearing message setting'); - return; - } - message.set({ unread: 0, // mark the message as read. }); @@ -325,10 +318,11 @@ async function handleExpirationTimerUpdateNoCommit( await conversation.updateExpireTimer({ providedExpirationType: expirationType, providedExpireTimer: expireTimer, - providedChangeTimestamp, + providedChangeTimestamp: lastDisappearingMessageChangeTimestamp, providedSource: source, receivedAt: message.get('received_at'), shouldCommit: false, + existingMessage: message, }); } @@ -338,14 +332,14 @@ export async function handleMessageJob( regularDataMessage: RegularMessageType, confirm: () => void, source: string, - messageHash: string, - expireUpdate?: any + messageHash: string ) { window?.log?.info( `Starting handleMessageJob for message ${messageModel.idForLogging()}, ${messageModel.get( 'serverTimestamp' ) || messageModel.get('timestamp')} in conversation ${conversation.idForLogging()}` ); + const sendingDeviceConversation = await getConversationController().getOrCreateAndWait( source, ConversationTypeEnum.PRIVATE @@ -353,36 +347,33 @@ export async function handleMessageJob( try { messageModel.set({ flags: regularDataMessage.flags }); - if (!isEmpty(expireUpdate)) { + if ( + messageModel.isIncoming() && + messageModel.get('expirationType') === 'deleteAfterSend' && + Boolean(messageModel.get('expirationStartTimestamp')) === false + ) { messageModel.set({ - expirationType: expireUpdate.expirationType, - expireTimer: expireUpdate.expireTimer, + expirationStartTimestamp: setExpirationStartTimestamp( + 'deleteAfterSend', + messageModel.get('sent_at') + ), }); - - if ( - messageModel.isIncoming() && - messageModel.get('expirationType') === 'deleteAfterSend' && - Boolean(messageModel.get('expirationStartTimestamp')) === false - ) { - messageModel.set({ - expirationStartTimestamp: setExpirationStartTimestamp( - 'deleteAfterSend', - messageModel.get('sent_at') - ), - }); - } } if (messageModel.isExpirationTimerUpdate()) { - // TODO account for lastDisappearingMessageChangeTimestamp - let expirationType = messageModel.get('expirationType'); - const expireTimer = messageModel.get('expireTimer'); + const expirationTimerUpdate = messageModel.get('expirationTimerUpdate'); + let expirationType = expirationTimerUpdate?.expirationType; + const expireTimer = expirationTimerUpdate?.expireTimer || 0; + const lastDisappearingMessageChangeTimestamp = + expirationTimerUpdate?.lastDisappearingMessageChangeTimestamp || getNowWithNetworkOffset(); + // TODO This could happen when we receive a legacy disappearing message if (!expirationType) { expirationType = conversation.isPrivate() ? 'deleteAfterRead' : 'deleteAfterSend'; } - // TODO compare types and change timestamps + // Compare mode and timestamp + const oldTypeValue = conversation.get('expirationType'); const oldTimerValue = conversation.get('expireTimer'); if (isEqual(expirationType, oldTypeValue) && isEqual(expireTimer, oldTimerValue)) { @@ -398,7 +389,8 @@ export async function handleMessageJob( messageModel, source, expirationType, - expireTimer + expireTimer, + lastDisappearingMessageChangeTimestamp ); } else { // this does not commit to db nor UI unless we need to approve a convo diff --git a/ts/session/messages/outgoing/ContentMessage.ts b/ts/session/messages/outgoing/ContentMessage.ts index 4dab60ab4..a43e06de6 100644 --- a/ts/session/messages/outgoing/ContentMessage.ts +++ b/ts/session/messages/outgoing/ContentMessage.ts @@ -10,5 +10,6 @@ export abstract class ContentMessage extends Message { public ttl(): number { return TTL_DEFAULT.TTL_MAX; } + public abstract contentProto(): SignalService.Content; } diff --git a/ts/session/messages/outgoing/DataMessage.ts b/ts/session/messages/outgoing/DataMessage.ts index 4508b9827..abfe20f07 100644 --- a/ts/session/messages/outgoing/DataMessage.ts +++ b/ts/session/messages/outgoing/DataMessage.ts @@ -1,11 +1,12 @@ -import { ContentMessage } from '.'; import { SignalService } from '../../../protobuf'; +import { ExpirableMessage } from './ExpirableMessage'; -export abstract class DataMessage extends ContentMessage { +export abstract class DataMessage extends ExpirableMessage { public abstract dataProto(): SignalService.DataMessage; public contentProto(): SignalService.Content { return new SignalService.Content({ + ...super.contentProto(), dataMessage: this.dataProto(), }); } diff --git a/ts/session/messages/outgoing/ExpirableMessage.ts b/ts/session/messages/outgoing/ExpirableMessage.ts new file mode 100644 index 000000000..c79f404b7 --- /dev/null +++ b/ts/session/messages/outgoing/ExpirableMessage.ts @@ -0,0 +1,34 @@ +import { SignalService } from '../../../protobuf'; +import { DisappearingMessageType } from '../../../util/expiringMessages'; +import { ContentMessage } from './ContentMessage'; +import { MessageParams } from './Message'; + +export interface ExpirableMessageParams extends MessageParams { + expirationType?: DisappearingMessageType; + expireTimer?: number; +} + +export class ExpirableMessage extends ContentMessage { + public readonly expirationType?: DisappearingMessageType; + public readonly expireTimer?: number; + + constructor(params: ExpirableMessageParams) { + super({ timestamp: params.timestamp, identifier: params.identifier }); + this.expirationType = params.expirationType; + this.expireTimer = params.expireTimer; + } + + public contentProto(): SignalService.Content { + return new SignalService.Content({ + expirationType: + this.expirationType === 'deleteAfterSend' + ? SignalService.Content.ExpirationType.DELETE_AFTER_SEND + : SignalService.Content.ExpirationType.DELETE_AFTER_READ, + expirationTimer: this.expireTimer, + }); + } + + public getDisappearingMessageType(): DisappearingMessageType | undefined { + return this.expirationType; + } +} diff --git a/ts/session/messages/outgoing/controlMessage/ExpirationTimerUpdateMessage.ts b/ts/session/messages/outgoing/controlMessage/ExpirationTimerUpdateMessage.ts index bc3cb6d4d..f4be6c8f3 100644 --- a/ts/session/messages/outgoing/controlMessage/ExpirationTimerUpdateMessage.ts +++ b/ts/session/messages/outgoing/controlMessage/ExpirationTimerUpdateMessage.ts @@ -1,23 +1,22 @@ import { SignalService } from '../../../../protobuf'; -import { DisappearingMessageType } from '../../../../util/expiringMessages'; import { PubKey } from '../../../types'; import { StringUtils } from '../../../utils'; -import { MessageParams } from '../Message'; -import { VisibleMessage } from '../visibleMessage/VisibleMessage'; +import { DataMessage } from '../DataMessage'; +import { ExpirableMessageParams } from '../ExpirableMessage'; -interface ExpirationTimerUpdateMessageParams extends MessageParams { +interface ExpirationTimerUpdateMessageParams extends ExpirableMessageParams { groupId?: string | PubKey; syncTarget?: string | PubKey; - expirationType: DisappearingMessageType | null; - expireTimer: number | null; lastDisappearingMessageChangeTimestamp: number | null; } // Note the old disappearing messages used a data message for the expiration time. // The new ones use properties on the Content Message // We will remove support for the old one 2 weeks after the release -export class ExpirationTimerUpdateMessage extends VisibleMessage { +export class ExpirationTimerUpdateMessage extends DataMessage { public readonly groupId?: PubKey; + public readonly syncTarget?: string; + // TODO should this typing be updated public readonly lastDisappearingMessageChangeTimestamp: number | null; constructor(params: ExpirationTimerUpdateMessageParams) { @@ -25,30 +24,26 @@ export class ExpirationTimerUpdateMessage extends VisibleMessage { timestamp: params.timestamp, identifier: params.identifier, expirationType: params.expirationType, - expireTimer: params.expireTimer || undefined, - syncTarget: params.syncTarget ? PubKey.cast(params.syncTarget).key : undefined, + expireTimer: params.expireTimer, }); this.lastDisappearingMessageChangeTimestamp = params.lastDisappearingMessageChangeTimestamp; const { groupId } = params; this.groupId = groupId ? PubKey.cast(groupId) : undefined; + this.syncTarget = params.syncTarget ? PubKey.cast(params.syncTarget).key : undefined; } public contentProto(): SignalService.Content { return new SignalService.Content({ + ...super.contentProto(), dataMessage: this.dataProto(), - expirationType: - this.expirationType === 'deleteAfterSend' - ? SignalService.Content.ExpirationType.DELETE_AFTER_SEND - : SignalService.Content.ExpirationType.DELETE_AFTER_READ, - expirationTimer: this.expireTimer, lastDisappearingMessageChangeTimestamp: this.lastDisappearingMessageChangeTimestamp, }); } public dataProto(): SignalService.DataMessage { - const data = super.dataProto(); + const data = new SignalService.DataMessage(); data.flags = SignalService.DataMessage.Flags.EXPIRATION_TIMER_UPDATE; @@ -65,7 +60,11 @@ export class ExpirationTimerUpdateMessage extends VisibleMessage { data.group = groupMessage; } - // TODO remove 2 weeks after the release + if (this.syncTarget) { + data.syncTarget = this.syncTarget; + } + + // TODO Legacy support remove 2 weeks after the release if (this.expireTimer) { data.expireTimer = this.expireTimer; } diff --git a/ts/session/messages/outgoing/visibleMessage/VisibleMessage.ts b/ts/session/messages/outgoing/visibleMessage/VisibleMessage.ts index 1c0bbdb10..58758527f 100644 --- a/ts/session/messages/outgoing/visibleMessage/VisibleMessage.ts +++ b/ts/session/messages/outgoing/visibleMessage/VisibleMessage.ts @@ -1,12 +1,10 @@ import ByteBuffer from 'bytebuffer'; import { isEmpty } from 'lodash'; -import { ContentMessage } from '..'; import { SignalService } from '../../../../protobuf'; import { LokiProfile } from '../../../../types/Message'; import { Reaction } from '../../../../types/Reaction'; -import { DisappearingMessageType } from '../../../../util/expiringMessages'; import { DURATION, TTL_DEFAULT } from '../../../constants'; -import { MessageParams } from '../Message'; +import { ExpirableMessage, ExpirableMessageParams } from '../ExpirableMessage'; interface AttachmentPointerCommon { contentType?: string; @@ -64,21 +62,17 @@ export interface Quote { attachments?: Array; } -export interface VisibleMessageParams extends MessageParams { +export interface VisibleMessageParams extends ExpirableMessageParams { attachments?: Array; body?: string; quote?: Quote; - expirationType?: DisappearingMessageType; - expireTimer?: number; lokiProfile?: LokiProfile; preview?: Array; reaction?: Reaction; syncTarget?: string; // undefined means it is not a synced message } -export class VisibleMessage extends ContentMessage { - public readonly expirationType?: DisappearingMessageType; - public readonly expireTimer?: number; +export class VisibleMessage extends ExpirableMessage { public readonly reaction?: Reaction; private readonly attachments?: Array; @@ -93,12 +87,15 @@ export class VisibleMessage extends ContentMessage { private readonly syncTarget?: string; constructor(params: VisibleMessageParams) { - super({ timestamp: params.timestamp, identifier: params.identifier }); + super({ + timestamp: params.timestamp, + identifier: params.identifier, + expirationType: params.expirationType, + expireTimer: params.expireTimer, + }); this.attachments = params.attachments; this.body = params.body; this.quote = params.quote; - this.expirationType = params.expirationType; - this.expireTimer = params.expireTimer; const profile = buildProfileForOutgoingMessage(params); @@ -112,12 +109,8 @@ export class VisibleMessage extends ContentMessage { public contentProto(): SignalService.Content { return new SignalService.Content({ + ...super.contentProto(), dataMessage: this.dataProto(), - expirationType: - this.expirationType === 'deleteAfterSend' - ? SignalService.Content.ExpirationType.DELETE_AFTER_SEND - : SignalService.Content.ExpirationType.DELETE_AFTER_READ, - expirationTimer: this.expireTimer, }); } @@ -200,6 +193,7 @@ export class VisibleMessage extends ContentMessage { return this.identifier === comparator.identifier && this.timestamp === comparator.timestamp; } + // TODO should this be on the Expirable message? Probably public ttl(): number { switch (this.expirationType) { case 'deleteAfterSend': diff --git a/ts/session/utils/syncUtils.ts b/ts/session/utils/syncUtils.ts index 8fc8ee92b..45d88cc82 100644 --- a/ts/session/utils/syncUtils.ts +++ b/ts/session/utils/syncUtils.ts @@ -324,6 +324,7 @@ export const buildSyncMessage = ( data: DataMessage | SignalService.DataMessage, syncTarget: string, sentTimestamp: number, + // TODO add proper types expireUpdate?: any ): VisibleMessage | ExpirationTimerUpdateMessage => { if ( diff --git a/ts/state/ducks/conversations.ts b/ts/state/ducks/conversations.ts index 053b7ef58..8f384fec0 100644 --- a/ts/state/ducks/conversations.ts +++ b/ts/state/ducks/conversations.ts @@ -130,12 +130,12 @@ export type PropsForGroupUpdateType = | PropsForGroupUpdateName | PropsForGroupUpdateLeft; -export type PropsForGroupUpdate = { +export interface PropsForGroupUpdate extends PropsForExpiringMessage { change: PropsForGroupUpdateType; messageId: string; receivedAt: number | undefined; isUnread: boolean; -}; +} export interface PropsForGroupInvitation extends PropsForExpiringMessage { serverName: string;