wip: added new legacy mode to panel, improved backwards compatibility

legacy mode support in conversation header, added typing for the expireUpdate, next is sending support
pull/2660/head
William Grant 3 years ago
parent 190357b006
commit 7d0673f7f2

@ -217,6 +217,8 @@
"disappearingMessagesModeAfterReadSubtitle": "Messages delete after they have been read.", "disappearingMessagesModeAfterReadSubtitle": "Messages delete after they have been read.",
"disappearingMessagesModeAfterSend": "Disappear After Send", "disappearingMessagesModeAfterSend": "Disappear After Send",
"disappearingMessagesModeAfterSendSubtitle": "Messages delete after they have been sent.", "disappearingMessagesModeAfterSendSubtitle": "Messages delete after they have been sent.",
"disappearingMessagesModeLegacy": "Legacy",
"disappearingMessagesModeLegacySubtitle": "Original version of disappearing messages.",
"disappearingMessagesDisabled": "Disappearing messages disabled", "disappearingMessagesDisabled": "Disappearing messages disabled",
"disabledDisappearingMessages": "$name$ has turned off disappearing messages.", "disabledDisappearingMessages": "$name$ has turned off disappearing messages.",
"youDisabledDisappearingMessages": "You have turned off disappearing messages.", "youDisabledDisappearingMessages": "You have turned off disappearing messages.",

@ -139,7 +139,12 @@ export const ConversationHeaderTitle = () => {
? null ? null
: expirationType === 'deleteAfterRead' : expirationType === 'deleteAfterRead'
? window.i18n('disappearingMessagesModeAfterRead') ? window.i18n('disappearingMessagesModeAfterRead')
: window.i18n('disappearingMessagesModeAfterSend'); : expirationType === 'deleteAfterSend'
? window.i18n('disappearingMessagesModeAfterSend')
: // legacy mode support
isGroup
? window.i18n('disappearingMessagesModeAfterSend')
: window.i18n('disappearingMessagesModeAfterRead');
const abbreviatedExpireTime = Boolean(expireTimer) const abbreviatedExpireTime = Boolean(expireTimer)
? ExpirationTimerOptions.getAbbreviated(expireTimer) ? ExpirationTimerOptions.getAbbreviated(expireTimer)
: null; : null;

@ -16,7 +16,10 @@ import {
getSelectedConversationExpirationSettings, getSelectedConversationExpirationSettings,
getSelectedConversationKey, getSelectedConversationKey,
} from '../../../../state/selectors/conversations'; } from '../../../../state/selectors/conversations';
import { DisappearingMessageConversationType } from '../../../../util/expiringMessages'; import {
DisappearingMessageConversationSetting,
DisappearingMessageConversationType,
} from '../../../../util/expiringMessages';
import { TimerOptionsArray } from '../../../../state/ducks/timerOptions'; import { TimerOptionsArray } from '../../../../state/ducks/timerOptions';
import { useTimerOptionsByMode } from '../../../../hooks/useParamSelector'; import { useTimerOptionsByMode } from '../../../../hooks/useParamSelector';
import { isEmpty } from 'lodash'; import { isEmpty } from 'lodash';
@ -108,14 +111,18 @@ const DisappearingModes = (props: DisappearingModesProps) => {
<PanelButtonGroup> <PanelButtonGroup>
{options.map((option: DisappearingMessageConversationType) => { {options.map((option: DisappearingMessageConversationType) => {
const optionI18n = const optionI18n =
option === 'off' option === 'legacy'
? window.i18n('disappearingMessagesModeOff') ? window.i18n('disappearingMessagesModeLegacy')
: option === 'deleteAfterRead' : option === 'deleteAfterRead'
? window.i18n('disappearingMessagesModeAfterRead') ? window.i18n('disappearingMessagesModeAfterRead')
: window.i18n('disappearingMessagesModeAfterSend'); : option === 'deleteAfterSend'
? window.i18n('disappearingMessagesModeAfterSend')
: window.i18n('disappearingMessagesModeOff');
const subtitleI18n = const subtitleI18n =
option === 'deleteAfterRead' option === 'legacy'
? window.i18n('disappearingMessagesModeLegacySubtitle')
: option === 'deleteAfterRead'
? window.i18n('disappearingMessagesModeAfterReadSubtitle') ? window.i18n('disappearingMessagesModeAfterReadSubtitle')
: option === 'deleteAfterSend' : option === 'deleteAfterSend'
? window.i18n('disappearingMessagesModeAfterSendSubtitle') ? window.i18n('disappearingMessagesModeAfterSendSubtitle')
@ -185,9 +192,19 @@ export const OverlayDisappearingMessages = () => {
return null; return null;
} }
const { isGroup } = convoProps;
const [modeSelected, setModeSelected] = useState(convoProps.expirationType); const [modeSelected, setModeSelected] = useState(convoProps.expirationType);
const [timeSelected, setTimeSelected] = useState(convoProps.expireTimer); const [timeSelected, setTimeSelected] = useState(convoProps.expireTimer);
const timerOptions = useTimerOptionsByMode(modeSelected); // Legacy mode uses the default timer options depending on the conversation type
// TODO verify that this if fine compared to updating in the useEffect
const timerOptions = useTimerOptionsByMode(
modeSelected === 'legacy'
? isGroup
? DisappearingMessageConversationSetting[2]
: DisappearingMessageConversationSetting[1]
: modeSelected
);
useEffect(() => { useEffect(() => {
if (modeSelected !== convoProps.expirationType) { if (modeSelected !== convoProps.expirationType) {

@ -1061,7 +1061,6 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
expireTimer = 0; expireTimer = 0;
} }
// TODO does this actually work?
if ( if (
this.get('lastDisappearingMessageChangeTimestamp') > lastDisappearingMessageChangeTimestamp this.get('lastDisappearingMessageChangeTimestamp') > lastDisappearingMessageChangeTimestamp
) { ) {
@ -1079,6 +1078,11 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
return; return;
} }
if (expirationType === 'legacy') {
// TODO If we are the new client then we ignore these updates
// TODO trigger UI
}
const isOutgoing = Boolean(!receivedAt); const isOutgoing = Boolean(!receivedAt);
source = source || UserUtils.getOurPubKeyStrFromCache(); source = source || UserUtils.getOurPubKeyStrFromCache();

@ -81,7 +81,11 @@ import {
loadPreviewData, loadPreviewData,
loadQuoteData, loadQuoteData,
} from '../types/MessageAttachment'; } from '../types/MessageAttachment';
import { ExpirationTimerOptions, setExpirationStartTimestamp } from '../util/expiringMessages'; import {
DisappearingMessageUpdate,
ExpirationTimerOptions,
setExpirationStartTimestamp,
} from '../util/expiringMessages';
import { Notifications } from '../util/notifications'; import { Notifications } from '../util/notifications';
import { Storage } from '../util/storage'; import { Storage } from '../util/storage';
import { LinkPreviews } from '../util/linkPreviews'; import { LinkPreviews } from '../util/linkPreviews';
@ -250,8 +254,6 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
return window.i18n('mediaMessage'); return window.i18n('mediaMessage');
} }
if (this.isExpirationTimerUpdate()) { if (this.isExpirationTimerUpdate()) {
// TODO Backwards compatibility for Disappearing Messages in old clients
// TODO What does this comment refer to mean?
const expireTimerUpdate = this.get('expirationTimerUpdate'); const expireTimerUpdate = this.get('expirationTimerUpdate');
const expirationType = expireTimerUpdate?.expirationType; const expirationType = expireTimerUpdate?.expirationType;
const expireTimer = expireTimerUpdate?.expireTimer; const expireTimer = expireTimerUpdate?.expireTimer;
@ -1070,28 +1072,28 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
sent: true, sent: true,
}); });
let expireUpdate = null; let expireUpdate: DisappearingMessageUpdate | null = null;
const expirationType = dataMessage.getDisappearingMessageType(); const expirationType = dataMessage.getDisappearingMessageType();
if (expirationType && contentMessage.expirationTimer) { if (expirationType && contentMessage.expirationTimer) {
expireUpdate = { expireUpdate = {
expirationType, expirationType,
expireTimer: contentMessage.expirationTimer, expireTimer: contentMessage.expirationTimer,
lastDisappearingMessageChangeTimestamp: lastDisappearingMessageChangeTimestamp: Number(
contentMessage.lastDisappearingMessageChangeTimestamp, contentMessage.lastDisappearingMessageChangeTimestamp
),
}; };
} }
await this.commit(); await this.commit();
await this.sendSyncMessage(dataMessage, now, expireUpdate); await this.sendSyncMessage(dataMessage, now, expireUpdate || undefined);
} }
public async sendSyncMessage( public async sendSyncMessage(
data: DataMessage | SignalService.DataMessage, data: DataMessage | SignalService.DataMessage,
sentTimestamp: number, sentTimestamp: number,
// TODO add proper types expireUpdate?: DisappearingMessageUpdate
expireUpdate?: any
) { ) {
if (this.get('synced') || this.get('sentSync')) { if (this.get('synced') || this.get('sentSync')) {
return; return;

@ -3,7 +3,7 @@ import { handleSwarmDataMessage } from './dataMessage';
import { removeFromCache, updateCache } from './cache'; import { removeFromCache, updateCache } from './cache';
import { SignalService } from '../protobuf'; import { SignalService } from '../protobuf';
import { compact, flatten, identity, isEmpty, isEqual, pickBy, toNumber } from 'lodash'; import { compact, flatten, identity, isEmpty, pickBy, toNumber } from 'lodash';
import { KeyPrefixType, PubKey } from '../session/types'; import { KeyPrefixType, PubKey } from '../session/types';
import { BlockedNumberController } from '../util/blockedNumberController'; import { BlockedNumberController } from '../util/blockedNumberController';
@ -30,6 +30,7 @@ import { findCachedBlindedMatchOrLookupOnAllServers } from '../session/apis/open
import { appendFetchAvatarAndProfileJob } from './userProfileImageUpdates'; import { appendFetchAvatarAndProfileJob } from './userProfileImageUpdates';
import { import {
DisappearingMessageConversationSetting, DisappearingMessageConversationSetting,
DisappearingMessageUpdate,
setExpirationStartTimestamp, setExpirationStartTimestamp,
} from '../util/expiringMessages'; } from '../util/expiringMessages';
@ -406,28 +407,26 @@ export async function innerHandleSwarmContentMessage(
perfStart(`handleSwarmDataMessage-${envelope.id}`); perfStart(`handleSwarmDataMessage-${envelope.id}`);
const expireUpdate = { // TODO Trigger banner in UI?
expirationType: DisappearingMessageConversationSetting[content.expirationType] || 'off', // TODO maybe trigger the banner later based on the expirationType for the conversation
// TODO rename to expirationTimer? const isLegacyMode = dataMessage.expireTimer && dataMessage.expireTimer > 0;
expireTimer: content.expirationTimer || 0,
// This is used for the expirationTimerUpdate if (isLegacyMode) {
window.log.info('WIP: Received legacy disappearing message', content);
}
const expireUpdate: DisappearingMessageUpdate = {
// TODO When sending a message if it's a legacy message then we need to set the type to the default for compatiblity reasons
expirationType: isLegacyMode
? DisappearingMessageConversationSetting[3]
: DisappearingMessageConversationSetting[content.expirationType] || 'off',
// TODO in the future we will remove the dataMessage expireTimer
expireTimer: isLegacyMode ? Number(dataMessage.expireTimer) : content.expirationTimer,
lastDisappearingMessageChangeTimestamp: content.lastDisappearingMessageChangeTimestamp lastDisappearingMessageChangeTimestamp: content.lastDisappearingMessageChangeTimestamp
? Number(content.lastDisappearingMessageChangeTimestamp) ? Number(content.lastDisappearingMessageChangeTimestamp)
: undefined, : undefined,
}; };
// TODO in the future we will remove the dataMessage expireTimer
// Backwards compatibility for Disappearing Messages in old clients
if (
expireUpdate.expireTimer > 0 &&
dataMessage.expireTimer &&
!isEqual(expireUpdate.expireTimer, dataMessage.expireTimer)
) {
// TODO Trigger banner in UI?
expireUpdate.expireTimer = dataMessage.expireTimer;
window.log.info('WIP: Received outdated disappearing message data message', content);
}
await handleSwarmDataMessage( await handleSwarmDataMessage(
envelope, envelope,
sentAtTimestamp, sentAtTimestamp,

@ -23,6 +23,7 @@ import { toLogFormat } from '../types/attachments/Errors';
import { ConversationTypeEnum } from '../models/conversationAttributes'; import { ConversationTypeEnum } from '../models/conversationAttributes';
import { Reactions } from '../util/reactions'; import { Reactions } from '../util/reactions';
import { Action, Reaction } from '../types/Reaction'; import { Action, Reaction } from '../types/Reaction';
import { DisappearingMessageUpdate } from '../util/expiringMessages';
function cleanAttachment(attachment: any) { function cleanAttachment(attachment: any) {
return { return {
@ -154,8 +155,7 @@ export async function handleSwarmDataMessage(
rawDataMessage: SignalService.DataMessage, rawDataMessage: SignalService.DataMessage,
messageHash: string, messageHash: string,
senderConversationModel: ConversationModel, senderConversationModel: ConversationModel,
// TODO add proper types expireUpdate: DisappearingMessageUpdate
expireUpdate?: any
): Promise<void> { ): Promise<void> {
window.log.info('handleSwarmDataMessage'); window.log.info('handleSwarmDataMessage');
@ -255,7 +255,7 @@ export async function handleSwarmDataMessage(
}); });
// This message is conversation setting change message // This message is conversation setting change message
if (expireUpdate.lastDisappearingMessageChangeTimestamp) { if (lastDisappearingMessageChangeTimestamp) {
msgModel.set({ msgModel.set({
expirationTimerUpdate: { expirationTimerUpdate: {
expirationType, expirationType,

@ -1,7 +1,7 @@
import { queueAttachmentDownloads } from './attachments'; import { queueAttachmentDownloads } from './attachments';
import { Quote } from './types'; import { Quote } from './types';
import _, { isEqual } from 'lodash'; import _, { isEmpty, isEqual } from 'lodash';
import { getConversationController } from '../session/conversations'; import { getConversationController } from '../session/conversations';
import { ConversationModel } from '../models/conversation'; import { ConversationModel } from '../models/conversation';
import { MessageModel, sliceQuoteText } from '../models/message'; import { MessageModel, sliceQuoteText } from '../models/message';
@ -364,18 +364,17 @@ export async function handleMessageJob(
if (messageModel.isExpirationTimerUpdate()) { if (messageModel.isExpirationTimerUpdate()) {
const expirationTimerUpdate = messageModel.get('expirationTimerUpdate'); const expirationTimerUpdate = messageModel.get('expirationTimerUpdate');
let expirationType = expirationTimerUpdate?.expirationType; if (!expirationTimerUpdate || isEmpty(expirationTimerUpdate)) {
const expireTimer = expirationTimerUpdate?.expireTimer || 0; window.log.info(`WIP: There is a problem with the expiration timer update`, messageModel);
const lastDisappearingMessageChangeTimestamp = return;
expirationTimerUpdate?.lastDisappearingMessageChangeTimestamp || getNowWithNetworkOffset();
// TODO This could happen when we receive a legacy disappearing message
if (!expirationType) {
expirationType = conversation.isPrivate() ? 'deleteAfterRead' : 'deleteAfterSend';
} }
// Compare mode and timestamp const expirationType = expirationTimerUpdate.expirationType || 'off';
const expireTimer = expirationTimerUpdate.expireTimer;
const lastDisappearingMessageChangeTimestamp =
expirationTimerUpdate.lastDisappearingMessageChangeTimestamp || getNowWithNetworkOffset();
// Compare mode and timestamp
const oldTypeValue = conversation.get('expirationType'); const oldTypeValue = conversation.get('expirationType');
const oldTimerValue = conversation.get('expireTimer'); const oldTimerValue = conversation.get('expireTimer');
if (isEqual(expirationType, oldTypeValue) && isEqual(expireTimer, oldTimerValue)) { if (isEqual(expirationType, oldTypeValue) && isEqual(expireTimer, oldTimerValue)) {

@ -27,7 +27,7 @@ import { UnsendMessage } from '../messages/outgoing/controlMessage/UnsendMessage
import { MessageRequestResponse } from '../messages/outgoing/controlMessage/MessageRequestResponse'; import { MessageRequestResponse } from '../messages/outgoing/controlMessage/MessageRequestResponse';
import { PubKey } from '../types'; import { PubKey } from '../types';
import { DataMessage } from '../messages/outgoing'; import { DataMessage } from '../messages/outgoing';
import { DisappearingMessageType } from '../../util/expiringMessages'; import { DisappearingMessageUpdate } from '../../util/expiringMessages';
const ITEM_ID_LAST_SYNC_TIMESTAMP = 'lastSyncedTimestamp'; const ITEM_ID_LAST_SYNC_TIMESTAMP = 'lastSyncedTimestamp';
@ -296,16 +296,16 @@ const buildSyncVisibleMessage = (
const buildSyncExpireTimerMessage = ( const buildSyncExpireTimerMessage = (
identifier: string, identifier: string,
expirationType: DisappearingMessageType, expireUpdate: DisappearingMessageUpdate,
expireTimer: number,
lastDisappearingMessageChangeTimestamp: number | null,
timestamp: number, timestamp: number,
syncTarget: string syncTarget: string
) => { ) => {
const { expirationType, expireTimer, lastDisappearingMessageChangeTimestamp } = expireUpdate;
return new ExpirationTimerUpdateMessage({ return new ExpirationTimerUpdateMessage({
identifier, identifier,
timestamp, timestamp,
expirationType: expirationType || null, expirationType,
expireTimer, expireTimer,
lastDisappearingMessageChangeTimestamp: lastDisappearingMessageChangeTimestamp || null, lastDisappearingMessageChangeTimestamp: lastDisappearingMessageChangeTimestamp || null,
syncTarget, syncTarget,
@ -324,8 +324,7 @@ export const buildSyncMessage = (
data: DataMessage | SignalService.DataMessage, data: DataMessage | SignalService.DataMessage,
syncTarget: string, syncTarget: string,
sentTimestamp: number, sentTimestamp: number,
// TODO add proper types expireUpdate?: DisappearingMessageUpdate
expireUpdate?: any
): VisibleMessage | ExpirationTimerUpdateMessage => { ): VisibleMessage | ExpirationTimerUpdateMessage => {
if ( if (
(data as any).constructor.name !== 'DataMessage' && (data as any).constructor.name !== 'DataMessage' &&
@ -342,17 +341,13 @@ export const buildSyncMessage = (
// don't include our profileKey on syncing message. This is to be done by a ConfigurationMessage now // don't include our profileKey on syncing message. This is to be done by a ConfigurationMessage now
const timestamp = _.toNumber(sentTimestamp); const timestamp = _.toNumber(sentTimestamp);
if ( if (
expireUpdate &&
!isEmpty(expireUpdate) && !isEmpty(expireUpdate) &&
dataMessage.flags === SignalService.DataMessage.Flags.EXPIRATION_TIMER_UPDATE dataMessage.flags === SignalService.DataMessage.Flags.EXPIRATION_TIMER_UPDATE
) { ) {
return buildSyncExpireTimerMessage( return buildSyncExpireTimerMessage(identifier, expireUpdate, timestamp, syncTarget);
identifier, } else {
expireUpdate.expirationType, window.log.info(`WIP: Something went wrong when syncing a disappearing message`);
expireUpdate.expireTimer,
expireUpdate.lastDisappearingMessageChangeTimestamp,
timestamp,
syncTarget
);
} }
return buildSyncVisibleMessage(identifier, dataMessage, timestamp, syncTarget); return buildSyncVisibleMessage(identifier, dataMessage, timestamp, syncTarget);
}; };

@ -1184,12 +1184,15 @@ export const getSelectedConversationExpirationModes = createSelector(
(convo: ReduxConversationType | undefined) => { (convo: ReduxConversationType | undefined) => {
let modes = DisappearingMessageConversationSetting; let modes = DisappearingMessageConversationSetting;
// Note to Self and Closed Groups only support deleteAfterSend // Note to Self and Closed Groups only support deleteAfterSend and legacy modes
if (convo?.isMe || (convo?.isGroup && !convo.isPublic)) { if (convo?.isMe || (convo?.isGroup && !convo.isPublic)) {
// only deleteAfterSend // only deleteAfterSend
modes = [modes[0], modes[2]]; modes = [modes[0], modes[2], modes[modes.length - 1]];
} }
// Legacy mode is the 2nd option in the UI
modes = [modes[0], modes[modes.length - 1], ...modes.slice(1, modes.length - 1)];
return modes; return modes;
} }
); );
@ -1199,5 +1202,6 @@ export const getSelectedConversationExpirationSettings = createSelector(
(convo: ReduxConversationType | undefined) => ({ (convo: ReduxConversationType | undefined) => ({
expirationType: convo?.expirationType, expirationType: convo?.expirationType,
expireTimer: convo?.expireTimer, expireTimer: convo?.expireTimer,
isGroup: convo?.isGroup,
}) })
); );

@ -217,6 +217,8 @@ export type LocalizerKeys =
| 'disappearingMessagesModeAfterReadSubtitle' | 'disappearingMessagesModeAfterReadSubtitle'
| 'disappearingMessagesModeAfterSend' | 'disappearingMessagesModeAfterSend'
| 'disappearingMessagesModeAfterSendSubtitle' | 'disappearingMessagesModeAfterSendSubtitle'
| 'disappearingMessagesModeLegacy'
| 'disappearingMessagesModeLegacySubtitle'
| 'disappearingMessagesDisabled' | 'disappearingMessagesDisabled'
| 'disabledDisappearingMessages' | 'disabledDisappearingMessages'
| 'youDisabledDisappearingMessages' | 'youDisabledDisappearingMessages'

@ -13,9 +13,17 @@ import { getNowWithNetworkOffset } from '../session/apis/snode_api/SNodeAPI';
export const DisappearingMessageMode = ['deleteAfterRead', 'deleteAfterSend']; export const DisappearingMessageMode = ['deleteAfterRead', 'deleteAfterSend'];
export type DisappearingMessageType = typeof DisappearingMessageMode[number] | null; export type DisappearingMessageType = typeof DisappearingMessageMode[number] | null;
export const DisappearingMessageConversationSetting = ['off', ...DisappearingMessageMode]; export const DisappearingMessageConversationSetting = ['off', ...DisappearingMessageMode, 'legacy'];
export type DisappearingMessageConversationType = typeof DisappearingMessageConversationSetting[number]; export type DisappearingMessageConversationType = typeof DisappearingMessageConversationSetting[number];
export type DisappearingMessageUpdate = {
expirationType: DisappearingMessageType;
// TODO rename to expirationTimer?
expireTimer: number;
// This is used for the expirationTimerUpdate
lastDisappearingMessageChangeTimestamp?: number;
};
export async function destroyMessagesAndUpdateRedux( export async function destroyMessagesAndUpdateRedux(
messages: Array<{ messages: Array<{
conversationKey: string; conversationKey: string;

Loading…
Cancel
Save