feat: fixed double expiration update message issue

started creating an inheritable expiration message class, improved start triggering logic
pull/2660/head
William Grant 2 years ago
parent 446752cf54
commit 97ecc9e521

@ -5,7 +5,7 @@ import {
PropsForGroupUpdateType, PropsForGroupUpdateType,
} from '../../../../state/ducks/conversations'; } from '../../../../state/ducks/conversations';
import { NotificationBubble } from './notification-bubble/NotificationBubble'; import { NotificationBubble } from './notification-bubble/NotificationBubble';
import { ReadableMessage } from './ReadableMessage'; import { ExpirableReadableMessage } from './ExpirableReadableMessage';
import { arrayContainsUsOnly } from '../../../../models/message'; import { arrayContainsUsOnly } from '../../../../models/message';
import { useConversationsUsernameWithQuoteOrFullPubkey } from '../../../../hooks/useParamSelector'; import { useConversationsUsernameWithQuoteOrFullPubkey } from '../../../../hooks/useParamSelector';
@ -71,16 +71,29 @@ const ChangeItem = (change: PropsForGroupUpdateType): string => {
}; };
export const GroupUpdateMessage = (props: PropsForGroupUpdate) => { export const GroupUpdateMessage = (props: PropsForGroupUpdate) => {
const { change, messageId, receivedAt, isUnread } = props; const {
change,
messageId,
receivedAt,
isUnread,
direction,
expirationLength,
expirationTimestamp,
isExpired,
} = props;
return ( return (
<ReadableMessage <ExpirableReadableMessage
messageId={messageId} messageId={messageId}
receivedAt={receivedAt} receivedAt={receivedAt}
isUnread={isUnread} isUnread={isUnread}
direction={direction}
expirationLength={expirationLength}
expirationTimestamp={expirationTimestamp}
isExpired={isExpired}
key={`readable-message-${messageId}`} key={`readable-message-${messageId}`}
> >
<NotificationBubble notificationText={ChangeItem(change)} iconType="users" /> <NotificationBubble notificationText={ChangeItem(change)} iconType="users" />
</ReadableMessage> </ExpirableReadableMessage>
); );
}; };

@ -362,6 +362,7 @@ export async function setDisappearingMessagesByConvoId(
if (!expirationType || expirationType === 'off' || !seconds || seconds <= 0) { if (!expirationType || expirationType === 'off' || !seconds || seconds <= 0) {
await conversation.updateExpireTimer({ await conversation.updateExpireTimer({
providedExpirationType: 'off', providedExpirationType: 'off',
providedExpireTimer: 0,
providedChangeTimestamp, providedChangeTimestamp,
}); });
} else { } else {

@ -1036,17 +1036,20 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
receivedAt, // is set if it comes from outside receivedAt, // is set if it comes from outside
fromSync, fromSync,
shouldCommit = true, shouldCommit = true,
existingMessage,
}: { }: {
providedExpirationType: DisappearingMessageConversationType; providedExpirationType: DisappearingMessageConversationType;
providedExpireTimer?: number; providedExpireTimer?: number;
providedChangeTimestamp?: number; providedChangeTimestamp: number;
providedSource?: string; providedSource?: string;
receivedAt?: number; // is set if it comes from outside receivedAt?: number; // is set if it comes from outside
fromSync?: boolean; fromSync?: boolean;
shouldCommit?: boolean; shouldCommit?: boolean;
existingMessage?: MessageModel;
}): Promise<void> { }): Promise<void> {
let expirationType = providedExpirationType; let expirationType = providedExpirationType;
let expireTimer = providedExpireTimer; let expireTimer = providedExpireTimer;
const lastDisappearingMessageChangeTimestamp = providedChangeTimestamp;
let source = providedSource; let source = providedSource;
defaults({ fromSync }, { fromSync: false }); defaults({ fromSync }, { fromSync: false });
@ -1057,12 +1060,19 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
} }
// TODO does this actually work? // TODO does this actually work?
if (
this.get('lastDisappearingMessageChangeTimestamp') > lastDisappearingMessageChangeTimestamp
) {
window.log.info('WIP: updateExpireTimer() This is an outdated disappearing message setting');
return;
}
if ( if (
isEqual(expirationType, this.get('expirationType')) && isEqual(expirationType, this.get('expirationType')) &&
isEqual(expireTimer, this.get('expireTimer')) isEqual(expireTimer, this.get('expireTimer'))
) { ) {
window.log.info( 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; return;
} }
@ -1077,17 +1087,17 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
this.set({ this.set({
expirationType, expirationType,
expireTimer, 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(), id: this.idForLogging(),
expirationType, expirationType,
expireTimer, expireTimer,
lastDisappearingMessageChangeTimestamp,
source, source,
}); });
const lastDisappearingMessageChangeTimestamp = providedChangeTimestamp || 0;
const commonAttributes = { const commonAttributes = {
flags: SignalService.DataMessage.Flags.EXPIRATION_TIMER_UPDATE, flags: SignalService.DataMessage.Flags.EXPIRATION_TIMER_UPDATE,
expirationTimerUpdate: { expirationTimerUpdate: {
@ -1097,28 +1107,30 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
source, source,
fromSync, fromSync,
}, },
expirationType, expirationType: expirationType !== 'off' ? expirationType : undefined,
expireTimer, expireTimer: expirationType !== 'off' ? expireTimer : undefined,
}; };
let message: MessageModel | undefined; let message: MessageModel | undefined = existingMessage || undefined;
if (isOutgoing) { if (!message) {
message = await this.addSingleOutgoingMessage({ if (isOutgoing) {
...commonAttributes, message = await this.addSingleOutgoingMessage({
sent_at: timestamp, ...commonAttributes,
}); sent_at: timestamp,
} else { });
// TODO do we still want to handle expiration in incoming messages? } else {
message = await this.addSingleIncomingMessage({ // TODO do we still want to handle expiration in incoming messages?
...commonAttributes, message = await this.addSingleIncomingMessage({
// Even though this isn't reflected to the user, we want to place the last seen ...commonAttributes,
// indicator above it. We set it to 'unread' to trigger that placement. // Even though this isn't reflected to the user, we want to place the last seen
unread: 1, // indicator above it. We set it to 'unread' to trigger that placement.
source, unread: 1,
sent_at: timestamp, source,
received_at: timestamp, sent_at: timestamp,
}); received_at: timestamp,
});
}
} }
if (this.isActive()) { if (this.isActive()) {
@ -1144,6 +1156,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
if (this.isMe()) { if (this.isMe()) {
// TODO Check that the args are correct // 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') { if (expireUpdate.expirationType === 'deleteAfterRead') {
window.log.info('WIP: Note to Self messages cannot be delete after read!'); window.log.info('WIP: Note to Self messages cannot be delete after read!');
return; return;
@ -1159,7 +1172,6 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
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 {
// TODO Check that the args are correct
// Cannot be an open group // Cannot be an open group
window?.log?.warn('TODO: Expiration update for closed groups are to be updated'); window?.log?.warn('TODO: Expiration update for closed groups are to be updated');
const expireUpdateForGroup = { const expireUpdateForGroup = {

@ -250,18 +250,12 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
return window.i18n('mediaMessage'); return window.i18n('mediaMessage');
} }
if (this.isExpirationTimerUpdate()) { 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 expireTimerUpdate = this.get('expirationTimerUpdate');
const expirationType = this.get('expirationType'); const expirationType = expireTimerUpdate?.expirationType;
const expireTimer = this.get('expireTimer'); const expireTimer = expireTimerUpdate?.expireTimer;
if ( if (!expireTimerUpdate || expirationType === 'off' || !expireTimer || expireTimer === 0) {
!expireTimerUpdate ||
expireTimerUpdate.expirationType === 'off' ||
!expireTimerUpdate.expireTimer ||
expirationType === 'off' ||
!expireTimer ||
expireTimer === 0
) {
return window.i18n('disappearingMessagesDisabled'); return window.i18n('disappearingMessagesDisabled');
} }
@ -417,6 +411,7 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
messageId: this.id, messageId: this.id,
isUnread: this.isUnread(), isUnread: this.isUnread(),
receivedAt: this.get('received_at'), receivedAt: this.get('received_at'),
...this.getPropsForExpiringMessage(),
}; };
if (groupUpdate.joined?.length) { if (groupUpdate.joined?.length) {
@ -1063,20 +1058,20 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
} }
public async sendSyncMessageOnly(dataMessage: DataMessage) { public async sendSyncMessageOnly(dataMessage: DataMessage) {
const contentMessage = dataMessage.contentProto();
const now = Date.now(); const now = Date.now();
this.set({ this.set({
sent_to: [UserUtils.getOurPubKeyStrFromCache()], sent_to: [UserUtils.getOurPubKeyStrFromCache()],
sent: true, sent: true,
// NOTE if disappearing message is deleteAfterRead then we don't use this
expirationStartTimestamp: now,
}); });
const contentMessage = dataMessage.contentProto();
let expireUpdate = null; let expireUpdate = null;
const expirationType = dataMessage.getDisappearingMessageType();
if (contentMessage.expirationType && contentMessage.expirationTimer) { if (expirationType && contentMessage.expirationTimer) {
expireUpdate = { expireUpdate = {
expirationType: contentMessage.expirationType, expirationType,
expireTimer: contentMessage.expirationTimer, expireTimer: contentMessage.expirationTimer,
lastDisappearingMessageChangeTimestamp: lastDisappearingMessageChangeTimestamp:
contentMessage.lastDisappearingMessageChangeTimestamp, contentMessage.lastDisappearingMessageChangeTimestamp,
@ -1091,6 +1086,7 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
public async sendSyncMessage( public async sendSyncMessage(
data: DataMessage | SignalService.DataMessage, data: DataMessage | SignalService.DataMessage,
sentTimestamp: number, sentTimestamp: number,
// TODO add proper types
expireUpdate?: any expireUpdate?: any
) { ) {
if (this.get('synced') || this.get('sentSync')) { if (this.get('synced') || this.get('sentSync')) {
@ -1174,6 +1170,7 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
await this.commit(); await this.commit();
// the line below makes sure that getNextExpiringMessage will find this message as expiring. // 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 // getNextExpiringMessage is used on app start to clean already expired messages which should have been removed already, but are not
await this.setToExpire(); await this.setToExpire();
const convo = this.getConversation(); const convo = this.getConversation();

@ -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, pickBy, toNumber } from 'lodash'; import { compact, flatten, identity, isEmpty, isEqual, 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';
@ -406,31 +406,28 @@ export async function innerHandleSwarmContentMessage(
perfStart(`handleSwarmDataMessage-${envelope.id}`); perfStart(`handleSwarmDataMessage-${envelope.id}`);
let expireUpdate = null; const expireUpdate = {
expirationType: DisappearingMessageConversationSetting[content.expirationType] || 'off',
const expirationType = // TODO rename to expirationTimer?
DisappearingMessageConversationSetting[content.expirationType] || 'off'; expireTimer: content.expirationTimer || 0,
let 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 // TODO in the future we will remove the dataMessage expireTimer
// Backwards compatibility for Disappearing Messages in old clients // 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? // TODO Trigger banner in UI?
expireTimer = dataMessage.expireTimer; expireUpdate.expireTimer = dataMessage.expireTimer;
window.log.info('WIP: Received outdated disappearing message data message', content); 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( await handleSwarmDataMessage(
envelope, envelope,
sentAtTimestamp, sentAtTimestamp,

@ -154,7 +154,8 @@ export async function handleSwarmDataMessage(
rawDataMessage: SignalService.DataMessage, rawDataMessage: SignalService.DataMessage,
messageHash: string, messageHash: string,
senderConversationModel: ConversationModel, senderConversationModel: ConversationModel,
expireUpdate: any // TODO add proper types
expireUpdate?: any
): Promise<void> { ): Promise<void> {
window.log.info('handleSwarmDataMessage'); window.log.info('handleSwarmDataMessage');
@ -245,6 +246,25 @@ export async function handleSwarmDataMessage(
if (isSyncedMessage) { if (isSyncedMessage) {
// TODO handle sync messages separately // TODO handle sync messages separately
window.log.info('WIP: Sync Message dropping'); 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( await handleSwarmMessage(
@ -253,8 +273,7 @@ export async function handleSwarmDataMessage(
sentAtTimestamp, sentAtTimestamp,
cleanDataMessage, cleanDataMessage,
convoToAddMessageTo, convoToAddMessageTo,
() => removeFromCache(envelope), () => removeFromCache(envelope)
isSyncedMessage ? expireUpdate : null
); );
} }
@ -301,8 +320,7 @@ async function handleSwarmMessage(
sentAt: number, sentAt: number,
rawDataMessage: SignalService.DataMessage, rawDataMessage: SignalService.DataMessage,
convoToAddMessageTo: ConversationModel, convoToAddMessageTo: ConversationModel,
confirm: () => void, confirm: () => void
expireUpdate?: any
): Promise<void> { ): Promise<void> {
if (!rawDataMessage || !msgModel) { if (!rawDataMessage || !msgModel) {
window?.log?.warn('Invalid data passed to handleSwarmMessage.'); window?.log?.warn('Invalid data passed to handleSwarmMessage.');
@ -350,8 +368,7 @@ async function handleSwarmMessage(
toRegularMessage(rawDataMessage), toRegularMessage(rawDataMessage),
confirm, confirm,
msgModel.get('source'), msgModel.get('source'),
messageHash, messageHash
expireUpdate
); );
}); });
} }

@ -1,7 +1,7 @@
import { queueAttachmentDownloads } from './attachments'; import { queueAttachmentDownloads } from './attachments';
import { Quote } from './types'; import { Quote } from './types';
import _, { isEmpty, isEqual } from 'lodash'; import _, { 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';
@ -308,16 +308,9 @@ async function handleExpirationTimerUpdateNoCommit(
message: MessageModel, message: MessageModel,
source: string, source: string,
expirationType: DisappearingMessageConversationType, 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({ message.set({
unread: 0, // mark the message as read. unread: 0, // mark the message as read.
}); });
@ -325,10 +318,11 @@ async function handleExpirationTimerUpdateNoCommit(
await conversation.updateExpireTimer({ await conversation.updateExpireTimer({
providedExpirationType: expirationType, providedExpirationType: expirationType,
providedExpireTimer: expireTimer, providedExpireTimer: expireTimer,
providedChangeTimestamp, providedChangeTimestamp: lastDisappearingMessageChangeTimestamp,
providedSource: source, providedSource: source,
receivedAt: message.get('received_at'), receivedAt: message.get('received_at'),
shouldCommit: false, shouldCommit: false,
existingMessage: message,
}); });
} }
@ -338,14 +332,14 @@ export async function handleMessageJob(
regularDataMessage: RegularMessageType, regularDataMessage: RegularMessageType,
confirm: () => void, confirm: () => void,
source: string, source: string,
messageHash: string, messageHash: string
expireUpdate?: any
) { ) {
window?.log?.info( window?.log?.info(
`Starting handleMessageJob for message ${messageModel.idForLogging()}, ${messageModel.get( `Starting handleMessageJob for message ${messageModel.idForLogging()}, ${messageModel.get(
'serverTimestamp' 'serverTimestamp'
) || messageModel.get('timestamp')} in conversation ${conversation.idForLogging()}` ) || messageModel.get('timestamp')} in conversation ${conversation.idForLogging()}`
); );
const sendingDeviceConversation = await getConversationController().getOrCreateAndWait( const sendingDeviceConversation = await getConversationController().getOrCreateAndWait(
source, source,
ConversationTypeEnum.PRIVATE ConversationTypeEnum.PRIVATE
@ -353,36 +347,33 @@ export async function handleMessageJob(
try { try {
messageModel.set({ flags: regularDataMessage.flags }); messageModel.set({ flags: regularDataMessage.flags });
if (!isEmpty(expireUpdate)) { if (
messageModel.isIncoming() &&
messageModel.get('expirationType') === 'deleteAfterSend' &&
Boolean(messageModel.get('expirationStartTimestamp')) === false
) {
messageModel.set({ messageModel.set({
expirationType: expireUpdate.expirationType, expirationStartTimestamp: setExpirationStartTimestamp(
expireTimer: expireUpdate.expireTimer, '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()) { if (messageModel.isExpirationTimerUpdate()) {
// TODO account for lastDisappearingMessageChangeTimestamp const expirationTimerUpdate = messageModel.get('expirationTimerUpdate');
let expirationType = messageModel.get('expirationType'); let expirationType = expirationTimerUpdate?.expirationType;
const expireTimer = messageModel.get('expireTimer'); const expireTimer = expirationTimerUpdate?.expireTimer || 0;
const lastDisappearingMessageChangeTimestamp =
expirationTimerUpdate?.lastDisappearingMessageChangeTimestamp || getNowWithNetworkOffset();
// TODO This could happen when we receive a legacy disappearing message
if (!expirationType) { if (!expirationType) {
expirationType = conversation.isPrivate() ? 'deleteAfterRead' : 'deleteAfterSend'; expirationType = conversation.isPrivate() ? 'deleteAfterRead' : 'deleteAfterSend';
} }
// TODO compare types and change timestamps // 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)) {
@ -398,7 +389,8 @@ export async function handleMessageJob(
messageModel, messageModel,
source, source,
expirationType, expirationType,
expireTimer expireTimer,
lastDisappearingMessageChangeTimestamp
); );
} else { } else {
// this does not commit to db nor UI unless we need to approve a convo // this does not commit to db nor UI unless we need to approve a convo

@ -10,5 +10,6 @@ export abstract class ContentMessage extends Message {
public ttl(): number { public ttl(): number {
return TTL_DEFAULT.TTL_MAX; return TTL_DEFAULT.TTL_MAX;
} }
public abstract contentProto(): SignalService.Content; public abstract contentProto(): SignalService.Content;
} }

@ -1,11 +1,12 @@
import { ContentMessage } from '.';
import { SignalService } from '../../../protobuf'; 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 abstract dataProto(): SignalService.DataMessage;
public contentProto(): SignalService.Content { public contentProto(): SignalService.Content {
return new SignalService.Content({ return new SignalService.Content({
...super.contentProto(),
dataMessage: this.dataProto(), dataMessage: this.dataProto(),
}); });
} }

@ -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;
}
}

@ -1,23 +1,22 @@
import { SignalService } from '../../../../protobuf'; import { SignalService } from '../../../../protobuf';
import { DisappearingMessageType } from '../../../../util/expiringMessages';
import { PubKey } from '../../../types'; import { PubKey } from '../../../types';
import { StringUtils } from '../../../utils'; import { StringUtils } from '../../../utils';
import { MessageParams } from '../Message'; import { DataMessage } from '../DataMessage';
import { VisibleMessage } from '../visibleMessage/VisibleMessage'; import { ExpirableMessageParams } from '../ExpirableMessage';
interface ExpirationTimerUpdateMessageParams extends MessageParams { interface ExpirationTimerUpdateMessageParams extends ExpirableMessageParams {
groupId?: string | PubKey; groupId?: string | PubKey;
syncTarget?: string | PubKey; syncTarget?: string | PubKey;
expirationType: DisappearingMessageType | null;
expireTimer: number | null;
lastDisappearingMessageChangeTimestamp: number | null; lastDisappearingMessageChangeTimestamp: number | null;
} }
// Note the old disappearing messages used a data message for the expiration time. // Note the old disappearing messages used a data message for the expiration time.
// The new ones use properties on the Content Message // The new ones use properties on the Content Message
// We will remove support for the old one 2 weeks after the release // 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 groupId?: PubKey;
public readonly syncTarget?: string;
// TODO should this typing be updated
public readonly lastDisappearingMessageChangeTimestamp: number | null; public readonly lastDisappearingMessageChangeTimestamp: number | null;
constructor(params: ExpirationTimerUpdateMessageParams) { constructor(params: ExpirationTimerUpdateMessageParams) {
@ -25,30 +24,26 @@ export class ExpirationTimerUpdateMessage extends VisibleMessage {
timestamp: params.timestamp, timestamp: params.timestamp,
identifier: params.identifier, identifier: params.identifier,
expirationType: params.expirationType, expirationType: params.expirationType,
expireTimer: params.expireTimer || undefined, expireTimer: params.expireTimer,
syncTarget: params.syncTarget ? PubKey.cast(params.syncTarget).key : undefined,
}); });
this.lastDisappearingMessageChangeTimestamp = params.lastDisappearingMessageChangeTimestamp; this.lastDisappearingMessageChangeTimestamp = params.lastDisappearingMessageChangeTimestamp;
const { groupId } = params; const { groupId } = params;
this.groupId = groupId ? PubKey.cast(groupId) : undefined; this.groupId = groupId ? PubKey.cast(groupId) : undefined;
this.syncTarget = params.syncTarget ? PubKey.cast(params.syncTarget).key : undefined;
} }
public contentProto(): SignalService.Content { public contentProto(): SignalService.Content {
return new SignalService.Content({ return new SignalService.Content({
...super.contentProto(),
dataMessage: this.dataProto(), 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, lastDisappearingMessageChangeTimestamp: this.lastDisappearingMessageChangeTimestamp,
}); });
} }
public dataProto(): SignalService.DataMessage { public dataProto(): SignalService.DataMessage {
const data = super.dataProto(); const data = new SignalService.DataMessage();
data.flags = SignalService.DataMessage.Flags.EXPIRATION_TIMER_UPDATE; data.flags = SignalService.DataMessage.Flags.EXPIRATION_TIMER_UPDATE;
@ -65,7 +60,11 @@ export class ExpirationTimerUpdateMessage extends VisibleMessage {
data.group = groupMessage; 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) { if (this.expireTimer) {
data.expireTimer = this.expireTimer; data.expireTimer = this.expireTimer;
} }

@ -1,12 +1,10 @@
import ByteBuffer from 'bytebuffer'; import ByteBuffer from 'bytebuffer';
import { isEmpty } from 'lodash'; import { isEmpty } from 'lodash';
import { ContentMessage } from '..';
import { SignalService } from '../../../../protobuf'; import { SignalService } from '../../../../protobuf';
import { LokiProfile } from '../../../../types/Message'; import { LokiProfile } from '../../../../types/Message';
import { Reaction } from '../../../../types/Reaction'; import { Reaction } from '../../../../types/Reaction';
import { DisappearingMessageType } from '../../../../util/expiringMessages';
import { DURATION, TTL_DEFAULT } from '../../../constants'; import { DURATION, TTL_DEFAULT } from '../../../constants';
import { MessageParams } from '../Message'; import { ExpirableMessage, ExpirableMessageParams } from '../ExpirableMessage';
interface AttachmentPointerCommon { interface AttachmentPointerCommon {
contentType?: string; contentType?: string;
@ -64,21 +62,17 @@ export interface Quote {
attachments?: Array<QuotedAttachmentWithUrl>; attachments?: Array<QuotedAttachmentWithUrl>;
} }
export interface VisibleMessageParams extends MessageParams { export interface VisibleMessageParams extends ExpirableMessageParams {
attachments?: Array<AttachmentPointerWithUrl>; attachments?: Array<AttachmentPointerWithUrl>;
body?: string; body?: string;
quote?: Quote; quote?: Quote;
expirationType?: DisappearingMessageType;
expireTimer?: number;
lokiProfile?: LokiProfile; lokiProfile?: LokiProfile;
preview?: Array<PreviewWithAttachmentUrl>; preview?: Array<PreviewWithAttachmentUrl>;
reaction?: Reaction; reaction?: Reaction;
syncTarget?: string; // undefined means it is not a synced message syncTarget?: string; // undefined means it is not a synced message
} }
export class VisibleMessage extends ContentMessage { export class VisibleMessage extends ExpirableMessage {
public readonly expirationType?: DisappearingMessageType;
public readonly expireTimer?: number;
public readonly reaction?: Reaction; public readonly reaction?: Reaction;
private readonly attachments?: Array<AttachmentPointerWithUrl>; private readonly attachments?: Array<AttachmentPointerWithUrl>;
@ -93,12 +87,15 @@ export class VisibleMessage extends ContentMessage {
private readonly syncTarget?: string; private readonly syncTarget?: string;
constructor(params: VisibleMessageParams) { 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.attachments = params.attachments;
this.body = params.body; this.body = params.body;
this.quote = params.quote; this.quote = params.quote;
this.expirationType = params.expirationType;
this.expireTimer = params.expireTimer;
const profile = buildProfileForOutgoingMessage(params); const profile = buildProfileForOutgoingMessage(params);
@ -112,12 +109,8 @@ export class VisibleMessage extends ContentMessage {
public contentProto(): SignalService.Content { public contentProto(): SignalService.Content {
return new SignalService.Content({ return new SignalService.Content({
...super.contentProto(),
dataMessage: this.dataProto(), 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; return this.identifier === comparator.identifier && this.timestamp === comparator.timestamp;
} }
// TODO should this be on the Expirable message? Probably
public ttl(): number { public ttl(): number {
switch (this.expirationType) { switch (this.expirationType) {
case 'deleteAfterSend': case 'deleteAfterSend':

@ -324,6 +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?: any expireUpdate?: any
): VisibleMessage | ExpirationTimerUpdateMessage => { ): VisibleMessage | ExpirationTimerUpdateMessage => {
if ( if (

@ -130,12 +130,12 @@ export type PropsForGroupUpdateType =
| PropsForGroupUpdateName | PropsForGroupUpdateName
| PropsForGroupUpdateLeft; | PropsForGroupUpdateLeft;
export type PropsForGroupUpdate = { export interface PropsForGroupUpdate extends PropsForExpiringMessage {
change: PropsForGroupUpdateType; change: PropsForGroupUpdateType;
messageId: string; messageId: string;
receivedAt: number | undefined; receivedAt: number | undefined;
isUnread: boolean; isUnread: boolean;
}; }
export interface PropsForGroupInvitation extends PropsForExpiringMessage { export interface PropsForGroupInvitation extends PropsForExpiringMessage {
serverName: string; serverName: string;

Loading…
Cancel
Save