feat: timer notifications expire correctly when disappearing after sending

pull/2660/head
William Grant 2 years ago
parent f36bbb9c7c
commit 4ef3537a53

@ -3,7 +3,7 @@ import React from 'react';
import { missingCaseError } from '../../util/missingCaseError';
import { PropsForExpirationTimer } from '../../state/ducks/conversations';
import { NotificationBubble } from './message/message-item/notification-bubble/NotificationBubble';
import { ReadableMessage } from './message/message-item/ReadableMessage';
import { ExpirableReadableMessage } from './message/message-item/ExpirableReadableMessage';
export const TimerNotification = (props: PropsForExpirationTimer) => {
const {
@ -50,10 +50,15 @@ export const TimerNotification = (props: PropsForExpirationTimer) => {
throw new Error('textToRender invalid key used TimerNotification');
}
return (
<ReadableMessage
<ExpirableReadableMessage
convoId={props.convoId}
messageId={messageId}
direction={props.direction}
receivedAt={receivedAt}
isUnread={isUnread}
expirationLength={props.expirationLength}
expirationTimestamp={props.expirationTimestamp}
isExpired={props.isExpired}
key={`readable-message-${messageId}`}
>
<NotificationBubble
@ -61,6 +66,6 @@ export const TimerNotification = (props: PropsForExpirationTimer) => {
iconColor="inherit"
notificationText={textToRender}
/>
</ReadableMessage>
</ExpirableReadableMessage>
);
};

@ -1097,6 +1097,8 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
source,
fromSync,
},
expirationType,
expireTimer,
};
let message: MessageModel | undefined;

@ -280,6 +280,26 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
await deleteExternalMessageFiles(this.attributes);
}
public getPropsForExpiringMessage(): PropsForExpiringMessage | null {
const expirationType = this.get('expirationType');
const expirationLength = this.get('expireTimer') || null;
const expireTimerStart = this.get('expirationStartTimestamp') || null;
const expirationTimestamp =
expirationType && expireTimerStart && expirationLength
? expireTimerStart + expirationLength * DURATION.SECONDS
: null;
return {
convoId: this.get('conversationId'),
messageId: this.get('id'),
expirationLength,
expirationTimestamp,
isExpired: this.isExpired(),
};
}
public getPropsForTimerNotification(): PropsForExpirationTimer | null {
if (!this.isExpirationTimerUpdate()) {
return null;
@ -289,6 +309,12 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
return null;
}
// TODO should direction be parts of expiration props?
let direction = this.get('direction');
if (!direction) {
direction = this.get('type') === 'outgoing' ? 'outgoing' : 'incoming';
}
const { expirationType, expireTimer, fromSync, source } = timerUpdate;
const timespan = ExpirationTimerOptions.getName(expireTimer || 0);
const disabled = !expireTimer;
@ -302,29 +328,13 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
receivedAt: this.get('received_at'),
isUnread: this.isUnread(),
expirationType: expirationType || 'off',
direction,
...this.getPropsForExpiringMessage(),
};
return basicProps;
}
public getPropsForExpiringMessage(): PropsForExpiringMessage | null {
const expirationType = this.get('expirationType');
const expireTimerStart = this.get('expirationStartTimestamp') || null;
const expirationLength = this.get('expireTimer') || null;
const expirationTimestamp =
expirationType && expireTimerStart && expirationLength
? expireTimerStart + expirationLength * DURATION.SECONDS
: null;
return {
convoId: this.get('conversationId'),
messageId: this.get('id'),
expirationLength,
expirationTimestamp,
isExpired: this.isExpired(),
};
}
public getPropsForGroupInvitation(): PropsForGroupInvitation | null {
if (!this.isGroupInvitation()) {
return null;

@ -23,7 +23,6 @@ import { toLogFormat } from '../types/attachments/Errors';
import { ConversationTypeEnum } from '../models/conversationAttributes';
import { Reactions } from '../util/reactions';
import { Action, Reaction } from '../types/Reaction';
import { setExpirationStartTimestamp } from '../util/expiringMessages';
function cleanAttachment(attachment: any) {
return {
@ -246,12 +245,7 @@ export async function handleSwarmDataMessage(
if (isSyncedMessage) {
// TODO handle sync messages separately
window.log.info('WIP: Sync Message dropping');
} else {
if (msgModel.isIncoming() && expireUpdate.expirationType === 'deleteAfterSend') {
msgModel =
setExpirationStartTimestamp(msgModel, 'deleteAfterSend', msgModel.get('sent_at')) ||
msgModel;
}
expireUpdate = null;
}
await handleSwarmMessage(

@ -16,7 +16,10 @@ import { GoogleChrome } from '../util';
import { appendFetchAvatarAndProfileJob } from './userProfileImageUpdates';
import { ConversationTypeEnum } from '../models/conversationAttributes';
import { getUsBlindedInThatServer } from '../session/apis/open_group_api/sogsv3/knownBlindedkeys';
import { DisappearingMessageConversationType } from '../util/expiringMessages';
import {
DisappearingMessageConversationType,
setExpirationStartTimestamp,
} from '../util/expiringMessages';
import { getNowWithNetworkOffset } from '../session/apis/snode_api/SNodeAPI';
function contentTypeSupported(type: string): boolean {
@ -351,26 +354,43 @@ export async function handleMessageJob(
try {
messageModel.set({ flags: regularDataMessage.flags });
if (messageModel.isExpirationTimerUpdate()) {
// TODO account for lastDisappearingMessageChangeTimestamp
// TODO in the future we will remove the dataMessage expireTimer and the expirationTimerUpdate
let expirationType = expireUpdate.expirationType;
let expireTimer = expireUpdate.expireTimer;
if (!expirationType) {
expirationType = conversation.isPrivate() ? 'deleteAfterRead' : 'deleteAfterSend';
if (!isEmpty(expireUpdate)) {
messageModel.set({
expirationType: expireUpdate.expirationType,
expireTimer: expireUpdate.expireTimer,
});
if (messageModel.isIncoming() && messageModel.get('expirationType') === 'deleteAfterSend') {
messageModel =
setExpirationStartTimestamp(
messageModel,
'deleteAfterSend',
messageModel.get('sent_at')
) || messageModel;
}
}
if (messageModel.isExpirationTimerUpdate()) {
// TODO in the future we will remove the dataMessage expireTimer and the expirationTimerUpdate
// Backwards compatibility for Disappearing Messages in old clients
if (regularDataMessage.expireTimer) {
const expirationTimerUpdate = messageModel.get('expirationTimerUpdate');
if (!isEmpty(expirationTimerUpdate)) {
expirationType = expirationTimerUpdate?.expirationType;
expireTimer = expirationTimerUpdate?.expireTimer;
messageModel.set({
expirationType: expirationTimerUpdate?.expirationType,
expireTimer: expirationTimerUpdate?.expireTimer,
});
}
}
// TODO account for lastDisappearingMessageChangeTimestamp
let expirationType = messageModel.get('expirationType');
const expireTimer = messageModel.get('expireTimer');
if (!expirationType) {
expirationType = conversation.isPrivate() ? 'deleteAfterRead' : 'deleteAfterSend';
}
// TODO compare types and change timestamps
const oldTypeValue = conversation.get('expirationType');
const oldTimerValue = conversation.get('expireTimer');
@ -390,13 +410,6 @@ export async function handleMessageJob(
expireTimer
);
} else {
// NOTE this is a disappearing message NOT a expiration timer update
if (!isEmpty(expireUpdate)) {
messageModel.set({
expirationType: expireUpdate.expirationType,
expireTimer: expireUpdate.expireTimer,
});
}
// this does not commit to db nor UI unless we need to approve a convo
await handleRegularMessage(
conversation,

@ -1,9 +1,9 @@
import { DataMessage } from '..';
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';
interface ExpirationTimerUpdateMessageParams extends MessageParams {
groupId?: string | PubKey;
@ -16,22 +16,23 @@ interface ExpirationTimerUpdateMessageParams extends MessageParams {
// 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 DataMessage {
export class ExpirationTimerUpdateMessage extends VisibleMessage {
public readonly groupId?: PubKey;
public readonly syncTarget?: string;
public readonly expirationType: DisappearingMessageType | null;
public readonly expireTimer: number | null;
public readonly lastDisappearingMessageChangeTimestamp: number | null;
constructor(params: ExpirationTimerUpdateMessageParams) {
super({ timestamp: params.timestamp, identifier: params.identifier });
this.expirationType = params.expirationType;
this.expireTimer = params.expireTimer;
super({
timestamp: params.timestamp,
identifier: params.identifier,
expirationType: params.expirationType,
expireTimer: params.expireTimer || undefined,
syncTarget: params.syncTarget ? PubKey.cast(params.syncTarget).key : undefined,
});
this.lastDisappearingMessageChangeTimestamp = params.lastDisappearingMessageChangeTimestamp;
const { groupId, syncTarget } = params;
const { groupId } = params;
this.groupId = groupId ? PubKey.cast(groupId) : undefined;
this.syncTarget = syncTarget ? PubKey.cast(syncTarget).key : undefined;
}
public contentProto(): SignalService.Content {
@ -47,7 +48,7 @@ export class ExpirationTimerUpdateMessage extends DataMessage {
}
public dataProto(): SignalService.DataMessage {
const data = new SignalService.DataMessage();
const data = super.dataProto();
data.flags = SignalService.DataMessage.Flags.EXPIRATION_TIMER_UPDATE;
@ -64,10 +65,6 @@ export class ExpirationTimerUpdateMessage extends DataMessage {
data.group = groupMessage;
}
if (this.syncTarget) {
data.syncTarget = this.syncTarget;
}
// TODO remove 2 weeks after the release
if (this.expireTimer) {
data.expireTimer = this.expireTimer;

@ -136,9 +136,10 @@ async function handleMessageSentSuccess(
if (!shouldMarkMessageAsSynced) {
const expirationType = fetchedMessage.get('expirationType');
if (expirationType) {
if (expirationType === 'deleteAfterSend') {
fetchedMessage =
setExpirationStartTimestamp(fetchedMessage, expirationType) || fetchedMessage;
setExpirationStartTimestamp(fetchedMessage, expirationType, effectiveTimestamp) ||
fetchedMessage;
}
}
@ -169,11 +170,6 @@ async function handleMessageSentFailure(
}
}
const expirationType = fetchedMessage.get('expirationType');
if (expirationType) {
fetchedMessage = setExpirationStartTimestamp(fetchedMessage, expirationType) || fetchedMessage;
}
// always mark the message as sent.
// the fact that we have errors on the sent is based on the saveErrors()
fetchedMessage.set({

@ -83,7 +83,7 @@ export type PropsForExpiringMessage = {
isExpired?: boolean;
};
export type PropsForExpirationTimer = {
export interface PropsForExpirationTimer extends PropsForExpiringMessage {
expirationType: DisappearingMessageConversationType;
timespan: string;
disabled: boolean;
@ -96,7 +96,8 @@ export type PropsForExpirationTimer = {
messageId: string;
isUnread: boolean;
receivedAt: number | undefined;
};
direction: MessageModelType;
}
export type PropsForGroupUpdateGeneral = {
type: 'general';

@ -203,33 +203,42 @@ export function setExpirationStartTimestamp(
timestamp?: number
): MessageModel | null {
if (message.get('expirationStartTimestamp') > 0) {
window.log.info(`WIP: Expiration Timer already set. Ignoring.`);
window.log.info(`WIP: Expiration Timer already set. Ignoring new value.`);
return null;
}
let expirationStartTimestamp = getNowWithNetworkOffset();
if (timestamp) {
expirationStartTimestamp = Math.min(getNowWithNetworkOffset(), timestamp);
window.log.info(
`WIP: We compare 2 timestamps for a delete after ${
mode === 'deleteAfterRead' ? 'read' : 'send'
} message: \expirationStartTimestamp `,
new Date(expirationStartTimestamp).toLocaleTimeString(),
'\ntimestamp ',
new Date(timestamp).toLocaleTimeString()
);
expirationStartTimestamp = Math.min(expirationStartTimestamp, timestamp);
}
message.set('expirationStartTimestamp', expirationStartTimestamp);
if (mode === 'deleteAfterRead') {
window.log.info(
`WIP: setExpirationStartTimestamp we set the start timestamp for a delete after read message`,
`WIP: We set the start timestamp for a delete after read message to ${new Date(
expirationStartTimestamp
).toLocaleTimeString()}`,
message
);
} else if (mode === 'deleteAfterSend') {
window.log.info(
`WIP: setExpirationStartTimestamp we set the start timestamp for a delete after send message`,
`WIP: We set the start timestamp for a delete after send message to ${new Date(
expirationStartTimestamp
).toLocaleTimeString()}`,
message
);
} else {
console.log(
`WIP: setExpirationStartTimestamp Invalid disappearing message mode set. Ignoring.`,
message
);
console.log(`WIP: Invalid disappearing message mode set. Ignoring.`, message);
return null;
}

Loading…
Cancel
Save