diff --git a/ts/models/conversation.ts b/ts/models/conversation.ts index 6b2d2cc96..113429313 100644 --- a/ts/models/conversation.ts +++ b/ts/models/conversation.ts @@ -1198,7 +1198,6 @@ export class ConversationModel extends Backbone.Model { } if (this.isPrivate()) { - // debugger; const expirationTimerMessage = new ExpirationTimerUpdateMessage(expireUpdate); const pubkey = new PubKey(this.get('id')); await getMessageQueue().sendToPubKey(pubkey, expirationTimerMessage); diff --git a/ts/receiver/contentMessage.ts b/ts/receiver/contentMessage.ts index 29d881eb8..c8b6c2b80 100644 --- a/ts/receiver/contentMessage.ts +++ b/ts/receiver/contentMessage.ts @@ -29,12 +29,10 @@ import { ConversationTypeEnum } from '../models/conversationAttributes'; import { findCachedBlindedMatchOrLookupOnAllServers } from '../session/apis/open_group_api/sogsv3/knownBlindedkeys'; import { appendFetchAvatarAndProfileJob } from './userProfileImageUpdates'; import { - DisappearingMessageConversationSetting, - DisappearingMessageUpdate, - DisappearingMessageUtils, + checkForExpireUpdate, + checkHasOutdatedClient, setExpirationStartTimestamp, } from '../util/expiringMessages'; -import { checkIsFeatureReleased } from '../util/releaseFeature'; export async function handleSwarmContentMessage(envelope: EnvelopePlus, messageHash: string) { try { @@ -404,100 +402,34 @@ export async function innerHandleSwarmContentMessage( } if (content.dataMessage) { - const dataMessage = content.dataMessage; // because typescript is funky with incoming protobufs - if (dataMessage.profileKey && dataMessage.profileKey.length === 0) { - dataMessage.profileKey = null; + if (content.dataMessage.profileKey && content.dataMessage.profileKey.length === 0) { + content.dataMessage.profileKey = null; } - // TODO legacy messages support will be removed in a future release - // We will only support legacy disappearing messages for a short period before disappearing messages v2 is unlocked - const isDisappearingMessagesV2Released = await checkIsFeatureReleased( - 'Disappearing Messages V2' - ); - - const isLegacyContentMessage = DisappearingMessageUtils.isLegacyContentMessage(content); - const isLegacyMessage = Boolean( - isLegacyContentMessage && - DisappearingMessageUtils.isLegacyDataMessage(dataMessage as SignalService.DataMessage) - ); - // NOTE When a legacy client sends a Conversation Setting Message dataMessage.expirationType and dataMessage.expireTimer can possibly be undefined. - const isLegacyConversationSettingMessage = Boolean( - isLegacyContentMessage && - dataMessage.flags === SignalService.DataMessage.Flags.EXPIRATION_TIMER_UPDATE - ); - - debugger; - - let expirationTimer = isDisappearingMessagesV2Released - ? content.expirationTimer - : isLegacyMessage - ? Number(dataMessage.expireTimer) - : content.expirationTimer; - // TODO legacy messages support will be removed in a future release - // TODO so close! just have to fix the case of turning off disappearing messages in modern clients. Probably if the timer is zero then override any settings. - let expirationType = - expirationTimer === 0 - ? 'off' - : isDisappearingMessagesV2Released - ? DisappearingMessageConversationSetting[ - isLegacyContentMessage ? 3 : content.expirationType - ] - : DisappearingMessageConversationSetting[3]; - const lastDisappearingMessageChangeTimestamp = content.lastDisappearingMessageChangeTimestamp - ? Number(content.lastDisappearingMessageChangeTimestamp) - : undefined; + const expireUpdate = await checkForExpireUpdate(conversationModelForUIUpdate, content); - // TODO legacy messages support will be removed in a future release - // if it is a legacy message and disappearing messages v2 is released then we ignore it and use the local client's conversation settings - if (isDisappearingMessagesV2Released && isLegacyContentMessage) { - window.log.info(`WIP: received a legacy disappearing message after v2 was released.`); - expirationType = conversationModelForUIUpdate.get('expirationType'); - expirationTimer = conversationModelForUIUpdate.get('expireTimer'); + if (expireUpdate && !isEmpty(expireUpdate)) { + // TODO legacy messages support will be removed in a future release + checkHasOutdatedClient(conversationModelForUIUpdate, senderConversationModel, expireUpdate); } - const expireUpdate: DisappearingMessageUpdate = { - expirationType, - expirationTimer, - lastDisappearingMessageChangeTimestamp, - isLegacyConversationSettingMessage, - isLegacyMessage, - isDisappearingMessagesV2Released, - }; - - const outdatedSender = - senderConversationModel.get('nickname') || - senderConversationModel.get('displayNameInProfile') || - senderConversationModel.get('id'); - - if (conversationModelForUIUpdate.get('hasOutdatedClient')) { - // trigger notice banner - if (isLegacyMessage || isLegacyConversationSettingMessage) { - if (conversationModelForUIUpdate.get('hasOutdatedClient') !== outdatedSender) { - conversationModelForUIUpdate.set({ - hasOutdatedClient: outdatedSender, - }); - } - } else { - conversationModelForUIUpdate.set({ - hasOutdatedClient: undefined, - }); - } - conversationModelForUIUpdate.commit(); - } else { - if (isLegacyMessage || isLegacyConversationSettingMessage) { - conversationModelForUIUpdate.set({ - hasOutdatedClient: outdatedSender, - }); - conversationModelForUIUpdate.commit(); - } + // TODO legacy messages support will be removed in a future release + if ( + expireUpdate?.isDisappearingMessagesV2Released && + (expireUpdate?.isLegacyConversationSettingMessage || + (expireUpdate?.isMismatchedMessage && + content.dataMessage.flags === SignalService.DataMessage.Flags.EXPIRATION_TIMER_UPDATE)) + ) { + window.log.info(`WIP: The legacy message is an expiration timer update. Ignoring it.`); + return; } perfStart(`handleSwarmDataMessage-${envelope.id}`); await handleSwarmDataMessage( envelope, sentAtTimestamp, - dataMessage as SignalService.DataMessage, + content.dataMessage as SignalService.DataMessage, messageHash, senderConversationModel, expireUpdate diff --git a/ts/receiver/dataMessage.ts b/ts/receiver/dataMessage.ts index 24017c4f4..14f412471 100644 --- a/ts/receiver/dataMessage.ts +++ b/ts/receiver/dataMessage.ts @@ -23,7 +23,7 @@ import { toLogFormat } from '../types/attachments/Errors'; import { ConversationTypeEnum } from '../models/conversationAttributes'; import { Reactions } from '../util/reactions'; import { Action, Reaction } from '../types/Reaction'; -import { DisappearingMessageUpdate } from '../util/expiringMessages'; +import { DisappearingMessageUpdate, handleExpireUpdate } from '../util/expiringMessages'; function cleanAttachment(attachment: any) { return { @@ -155,7 +155,7 @@ export async function handleSwarmDataMessage( rawDataMessage: SignalService.DataMessage, messageHash: string, senderConversationModel: ConversationModel, - expireUpdate: DisappearingMessageUpdate + expireUpdate?: DisappearingMessageUpdate ): Promise { window.log.info('handleSwarmDataMessage'); @@ -229,7 +229,7 @@ export async function handleSwarmDataMessage( return; } - const msgModel = + let msgModel = isSyncedMessage || (envelope.senderIdentity && isUsFromCache(envelope.senderIdentity)) ? createSwarmMessageSentFromUs({ conversationId: convoIdToAddTheMessageTo, @@ -243,46 +243,12 @@ export async function handleSwarmDataMessage( sentAt: sentAtTimestamp, }); - if (isSyncedMessage) { - // TODO handle sync messages separately - window.log.info('WIP: Sync Message dropping'); - } else { - let { - expirationType, - // TODO renamed expireTimer to expirationTimer - expirationTimer: expireTimer, - lastDisappearingMessageChangeTimestamp, - isLegacyConversationSettingMessage, - isDisappearingMessagesV2Released, - } = expireUpdate; - - msgModel.set({ - expirationType, - expireTimer, - }); - - // TODO legacy messages support will be removed in a future release - // This message is conversation setting change message - if (lastDisappearingMessageChangeTimestamp || isLegacyConversationSettingMessage) { - if (isDisappearingMessagesV2Released && isLegacyConversationSettingMessage) { - window.log.info(`WIP: The legacy message is an expiration timer update. Ignoring it.`); - return; - } - - const expirationTimerUpdate = { - expirationType, - expireTimer, - lastDisappearingMessageChangeTimestamp: isLegacyConversationSettingMessage - ? isDisappearingMessagesV2Released - ? convoToAddMessageTo.get('lastDisappearingMessageChangeTimestamp') - : Date.now() - : Number(lastDisappearingMessageChangeTimestamp), - source: msgModel.get('source'), - }; - - msgModel.set({ - expirationTimerUpdate, - }); + if (expireUpdate && !isEmpty(expireUpdate)) { + if (isSyncedMessage) { + // TODO handle sync messages expiring separately + window.log.info('WIP: Sync Message dropping'); + } else { + msgModel = handleExpireUpdate(convoToAddMessageTo, msgModel, expireUpdate); } } diff --git a/ts/util/expiringMessages.ts b/ts/util/expiringMessages.ts index 574ba117c..e039ec79e 100644 --- a/ts/util/expiringMessages.ts +++ b/ts/util/expiringMessages.ts @@ -9,6 +9,9 @@ import { Data } from '../data/data'; import { getConversationController } from '../session/conversations'; import { getNowWithNetworkOffset } from '../session/apis/snode_api/SNodeAPI'; import { ProtobufUtils, SignalService } from '../protobuf'; +import { ConversationModel } from '../models/conversation'; +import { checkIsFeatureReleased } from './releaseFeature'; +import { MessageModel } from '../models/message'; // TODO Might need to be improved by using an enum // TODO do we need to add legacy here now that it's explicitly in the protbuf? @@ -28,30 +31,11 @@ export type DisappearingMessageUpdate = { expirationTimer: number; // This is used for the expirationTimerUpdate lastDisappearingMessageChangeTimestamp?: number; + // TODO legacy messages support will be removed in a future release isLegacyConversationSettingMessage?: boolean; isLegacyMessage?: boolean; isDisappearingMessagesV2Released?: boolean; -}; - -// TODO legacy messages support will be removed in a future release -// NOTE We need this to check for legacy disappearing messages where the expirationType and expireTimer should be undefined on the ContentMessage -function isLegacyContentMessage(contentMessage: SignalService.Content): boolean { - return ( - (contentMessage.expirationType === SignalService.Content.ExpirationType.UNKNOWN || - !ProtobufUtils.hasDefinedProperty(contentMessage, 'expirationType')) && - !ProtobufUtils.hasDefinedProperty(contentMessage, 'expirationTimer') - ); -} - -function isLegacyDataMessage(dataMessage: SignalService.DataMessage): boolean { - return ( - ProtobufUtils.hasDefinedProperty(dataMessage, 'expireTimer') && dataMessage.expireTimer > -1 - ); -} - -export const DisappearingMessageUtils = { - isLegacyContentMessage, - isLegacyDataMessage, + isMismatchedMessage?: boolean; }; export async function destroyMessagesAndUpdateRedux( @@ -277,3 +261,159 @@ export function setExpirationStartTimestamp( return expirationStartTimestamp; } + +// TODO legacy messages support will be removed in a future release +// NOTE We need this to check for legacy disappearing messages where the expirationType and expireTimer should be undefined on the ContentMessage +function checkIsLegacyContentMessage(contentMessage: SignalService.Content): boolean { + return ( + (contentMessage.expirationType === SignalService.Content.ExpirationType.UNKNOWN || + !ProtobufUtils.hasDefinedProperty(contentMessage, 'expirationType')) && + !ProtobufUtils.hasDefinedProperty(contentMessage, 'expirationTimer') + ); +} + +function checkIsLegacyDataMessage(dataMessage: SignalService.DataMessage): boolean { + return ( + ProtobufUtils.hasDefinedProperty(dataMessage, 'expireTimer') && dataMessage.expireTimer > -1 + ); +} + +// TODO legacy messages support will be removed in a future release +export async function checkForExpireUpdate( + convoToUpdate: ConversationModel, + content: SignalService.Content +): Promise { + const dataMessage = content.dataMessage as SignalService.DataMessage; + // We will only support legacy disappearing messages for a short period before disappearing messages v2 is unlocked + const isDisappearingMessagesV2Released = await checkIsFeatureReleased('Disappearing Messages V2'); + + const isLegacyContentMessage = checkIsLegacyContentMessage(content); + const isLegacyMessage = Boolean( + isLegacyContentMessage && checkIsLegacyDataMessage(dataMessage as SignalService.DataMessage) + ); + + let expirationTimer = isLegacyMessage ? Number(dataMessage.expireTimer) : content.expirationTimer; + let expirationType = + expirationTimer > 0 + ? DisappearingMessageConversationSetting[isLegacyContentMessage ? 3 : content.expirationType] + : DisappearingMessageConversationSetting[0]; + const lastDisappearingMessageChangeTimestamp = content.lastDisappearingMessageChangeTimestamp + ? Number(content.lastDisappearingMessageChangeTimestamp) + : undefined; + const isLegacyConversationSettingMessage = + isLegacyContentMessage && + isLegacyMessage && + dataMessage.flags === SignalService.DataMessage.Flags.EXPIRATION_TIMER_UPDATE; + + const isMismatchedMessage = + (!isLegacyConversationSettingMessage && + convoToUpdate.get('expirationType') !== 'off' && + convoToUpdate.get('expirationType') !== expirationType) || + (convoToUpdate.get('expireTimer') !== 0 && + convoToUpdate.get('expireTimer') !== expirationTimer); + + // If it is a legacy message and disappearing messages v2 is released then we ignore it and use the local client's conversation settings + if ( + isDisappearingMessagesV2Released && + (isLegacyMessage || + isLegacyConversationSettingMessage || + (!isLegacyConversationSettingMessage && isMismatchedMessage)) + ) { + window.log.info(`WIP: received a legacy disappearing message after v2 was released.`); + expirationType = convoToUpdate.get('expirationType'); + expirationTimer = convoToUpdate.get('expireTimer'); + } + + const expireUpdate: DisappearingMessageUpdate = { + expirationType, + expirationTimer, + lastDisappearingMessageChangeTimestamp, + isLegacyConversationSettingMessage, + isLegacyMessage, + isDisappearingMessagesV2Released, + isMismatchedMessage, + }; + + return expireUpdate; +} + +// TODO legacy messages support will be removed in a future release +export function handleExpireUpdate( + converationModel: ConversationModel, + messageModel: MessageModel, + expireUpdate: DisappearingMessageUpdate +) { + let { + expirationType, + // TODO renamed expireTimer to expirationTimer + expirationTimer: expireTimer, + lastDisappearingMessageChangeTimestamp, + isLegacyConversationSettingMessage, + isDisappearingMessagesV2Released, + } = expireUpdate; + + messageModel.set({ + expirationType, + expireTimer, + }); + + // This message is conversation setting change message + if (lastDisappearingMessageChangeTimestamp || isLegacyConversationSettingMessage) { + const expirationTimerUpdate = { + expirationType, + expireTimer, + lastDisappearingMessageChangeTimestamp: isLegacyConversationSettingMessage + ? isDisappearingMessagesV2Released + ? converationModel.get('lastDisappearingMessageChangeTimestamp') + : Date.now() + : Number(lastDisappearingMessageChangeTimestamp), + source: messageModel.get('source'), + }; + + messageModel.set({ + expirationTimerUpdate, + }); + } + + return messageModel; +} + +export function checkHasOutdatedClient( + convoToUpdate: ConversationModel, + sender: ConversationModel, + expireUpdate: DisappearingMessageUpdate +) { + const outdatedSender = + sender.get('nickname') || sender.get('displayNameInProfile') || sender.get('id'); + + if (convoToUpdate.get('hasOutdatedClient')) { + // trigger notice banner + if ( + expireUpdate.isLegacyMessage || + expireUpdate.isLegacyConversationSettingMessage || + (expireUpdate.isDisappearingMessagesV2Released && expireUpdate.isMismatchedMessage) + ) { + if (convoToUpdate.get('hasOutdatedClient') !== outdatedSender) { + convoToUpdate.set({ + hasOutdatedClient: outdatedSender, + }); + } + } else { + convoToUpdate.set({ + hasOutdatedClient: undefined, + }); + } + convoToUpdate.commit(); + } else { + if ( + expireUpdate.isLegacyMessage || + expireUpdate.isLegacyConversationSettingMessage || + (expireUpdate.isDisappearingMessagesV2Released && expireUpdate.isMismatchedMessage) + ) { + convoToUpdate.set({ + hasOutdatedClient: outdatedSender, + }); + convoToUpdate.commit(); + } + } +}