diff --git a/ts/components/conversation/message/message-item/ExpirableReadableMessage.tsx b/ts/components/conversation/message/message-item/ExpirableReadableMessage.tsx index d444a2509..510938923 100644 --- a/ts/components/conversation/message/message-item/ExpirableReadableMessage.tsx +++ b/ts/components/conversation/message/message-item/ExpirableReadableMessage.tsx @@ -94,6 +94,7 @@ export const ExpirableReadableMessage = (props: ExpirableReadableMessageProps) = expirationLength, expirationTimestamp, isExpired: props.isExpired, + direction: props.direction, }; const { isExpired } = useIsExpired(expiringProps); const isIncoming = direction === 'incoming'; diff --git a/ts/components/conversation/message/message-item/notification-bubble/CallNotification.tsx b/ts/components/conversation/message/message-item/notification-bubble/CallNotification.tsx index e1d8f73ec..6c32219e4 100644 --- a/ts/components/conversation/message/message-item/notification-bubble/CallNotification.tsx +++ b/ts/components/conversation/message/message-item/notification-bubble/CallNotification.tsx @@ -9,7 +9,7 @@ import { import { getSelectedConversation } from '../../../../../state/selectors/conversations'; import { LocalizerKeys } from '../../../../../types/LocalizerKeys'; import { SessionIconType } from '../../../../icon'; -import { ReadableMessage } from '../ReadableMessage'; +import { ExpirableReadableMessage } from '../ExpirableReadableMessage'; import { NotificationBubble } from './NotificationBubble'; type StyleType = Record< @@ -36,7 +36,16 @@ const style: StyleType = { }; export const CallNotification = (props: PropsForCallNotification) => { - const { messageId, receivedAt, isUnread, notificationType } = props; + const { + messageId, + receivedAt, + isUnread, + notificationType, + direction, + expirationLength, + expirationTimestamp, + isExpired, + } = props; const selectedConvoProps = useSelector(getSelectedConversation); @@ -54,10 +63,14 @@ export const CallNotification = (props: PropsForCallNotification) => { const iconColor = styleItem.iconColor; return ( - <ReadableMessage + <ExpirableReadableMessage messageId={messageId} receivedAt={receivedAt} + direction={direction} isUnread={isUnread} + expirationLength={expirationLength} + expirationTimestamp={expirationTimestamp} + isExpired={isExpired} key={`readable-message-${messageId}`} > <NotificationBubble @@ -65,6 +78,6 @@ export const CallNotification = (props: PropsForCallNotification) => { iconType={iconType} iconColor={iconColor} /> - </ReadableMessage> + </ExpirableReadableMessage> ); }; diff --git a/ts/models/conversation.ts b/ts/models/conversation.ts index 4ed57a15e..393bd2867 100644 --- a/ts/models/conversation.ts +++ b/ts/models/conversation.ts @@ -1215,6 +1215,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> { } } } + return this.addSingleMessage({ ...messageAttributes, conversationId: this.id, diff --git a/ts/models/message.ts b/ts/models/message.ts index 8339b8825..e7219a9bd 100644 --- a/ts/models/message.ts +++ b/ts/models/message.ts @@ -171,6 +171,7 @@ export class MessageModel extends Backbone.Model<MessageAttributes> { messageId: this.id, receivedAt: this.get('received_at') || Date.now(), isUnread: this.isUnread(), + ...this.getPropsForExpiringMessage(), }; } perfEnd(`getPropsMessage-${this.id}`, 'getPropsMessage'); @@ -280,7 +281,7 @@ export class MessageModel extends Backbone.Model<MessageAttributes> { await deleteExternalMessageFiles(this.attributes); } - public getPropsForExpiringMessage(): PropsForExpiringMessage | null { + public getPropsForExpiringMessage(): PropsForExpiringMessage | { direction: MessageModelType } { const expirationType = this.get('expirationType'); const expirationLength = this.get('expireTimer') || null; @@ -291,9 +292,13 @@ export class MessageModel extends Backbone.Model<MessageAttributes> { ? expireTimerStart + expirationLength * DURATION.SECONDS : null; + const direction = + this.get('direction') || this.get('type') === 'outgoing' ? 'outgoing' : 'incoming'; + return { convoId: this.get('conversationId'), messageId: this.get('id'), + direction, expirationLength, expirationTimestamp, isExpired: this.isExpired(), @@ -309,12 +314,6 @@ 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; @@ -328,7 +327,6 @@ export class MessageModel extends Backbone.Model<MessageAttributes> { receivedAt: this.get('received_at'), isUnread: this.isUnread(), expirationType: expirationType || 'off', - direction, ...this.getPropsForExpiringMessage(), }; @@ -357,7 +355,6 @@ export class MessageModel extends Backbone.Model<MessageAttributes> { return { serverName: invitation.name, url: serverAddress, - direction, acceptUrl: invitation.url, messageId: this.id as string, receivedAt: this.get('received_at'), @@ -1211,10 +1208,11 @@ export class MessageModel extends Backbone.Model<MessageAttributes> { if ( this.get('expirationType') === 'deleteAfterRead' && this.get('expireTimer') && - !this.get('expirationStartTimestamp') + Boolean(this.get('expirationStartTimestamp')) === false ) { - const message = setExpirationStartTimestamp(this, 'deleteAfterRead', readAt); - this.set({ expirationStartTimestamp: message?.get('expirationStartTimestamp') }); + this.set({ + expirationStartTimestamp: setExpirationStartTimestamp('deleteAfterRead', readAt), + }); } Notifications.clearByMessageId(this.id); diff --git a/ts/receiver/queuedJob.ts b/ts/receiver/queuedJob.ts index 051791ff8..c14d4e328 100644 --- a/ts/receiver/queuedJob.ts +++ b/ts/receiver/queuedJob.ts @@ -359,13 +359,17 @@ export async function handleMessageJob( expireTimer: expireUpdate.expireTimer, }); - if (messageModel.isIncoming() && messageModel.get('expirationType') === 'deleteAfterSend') { - messageModel = - setExpirationStartTimestamp( - messageModel, + if ( + messageModel.isIncoming() && + messageModel.get('expirationType') === 'deleteAfterSend' && + Boolean(messageModel.get('expirationStartTimestamp')) === false + ) { + messageModel.set({ + expirationStartTimestamp: setExpirationStartTimestamp( 'deleteAfterSend', messageModel.get('sent_at') - ) || messageModel; + ), + }); } } diff --git a/ts/session/sending/MessageSentHandler.ts b/ts/session/sending/MessageSentHandler.ts index bba995516..4c22ef19f 100644 --- a/ts/session/sending/MessageSentHandler.ts +++ b/ts/session/sending/MessageSentHandler.ts @@ -136,13 +136,13 @@ async function handleMessageSentSuccess( if (!shouldMarkMessageAsSynced) { const expirationType = fetchedMessage.get('expirationType'); - if (expirationType) { - fetchedMessage = - setExpirationStartTimestamp( - fetchedMessage, + if (expirationType && Boolean(fetchedMessage.get('expirationStartTimestamp')) === false) { + fetchedMessage.set({ + expirationStartTimestamp: setExpirationStartTimestamp( expirationType, expirationType === 'deleteAfterSend' ? effectiveTimestamp : undefined - ) || fetchedMessage; + ), + }); } } diff --git a/ts/session/utils/calling/CallManager.ts b/ts/session/utils/calling/CallManager.ts index ac44d4415..50cc54201 100644 --- a/ts/session/utils/calling/CallManager.ts +++ b/ts/session/utils/calling/CallManager.ts @@ -28,6 +28,7 @@ import { getCallMediaPermissionsSettings } from '../../../components/settings/Se import { PnServer } from '../../apis/push_notification_api'; import { getNowWithNetworkOffset } from '../../apis/snode_api/SNodeAPI'; import { approveConvoAndSendResponse } from '../../../interactions/conversationInteractions'; +import { setExpirationStartTimestamp } from '../../../util/expiringMessages'; // tslint:disable: function-name @@ -503,10 +504,16 @@ export async function USER_callRecipient(recipient: string) { calledConvo.set('active_at', Date.now()); // addSingleOutgoingMessage does the commit for us on the convo weAreCallerOnCurrentCall = true; + const expirationType = calledConvo.get('expirationType'); await calledConvo?.addSingleOutgoingMessage({ - sent_at: now, - expireTimer: 0, callNotificationType: 'started-call', + sent_at: now, + expirationType: expirationType !== 'off' ? expirationType : undefined, + expireTimer: calledConvo.get('expireTimer') ? calledConvo.get('expireTimer') : 0, + expirationStartTimestamp: setExpirationStartTimestamp( + expirationType, + expirationType === 'deleteAfterSend' ? now : undefined + ), }); // initiating a call is analogous to sending a message request @@ -841,13 +848,20 @@ export async function USER_acceptIncomingCallRequest(fromSender: string) { const networkTimestamp = getNowWithNetworkOffset(); const callerConvo = getConversationController().get(fromSender); callerConvo.set('active_at', networkTimestamp); + + const expirationType = callerConvo.get('expirationType'); await callerConvo?.addSingleIncomingMessage({ + callNotificationType: 'answered-a-call', source: UserUtils.getOurPubKeyStrFromCache(), sent_at: networkTimestamp, received_at: networkTimestamp, - expireTimer: 0, - callNotificationType: 'answered-a-call', unread: 0, + expirationType: expirationType !== 'off' ? expirationType : undefined, + expireTimer: callerConvo.get('expireTimer') ? callerConvo.get('expireTimer') : 0, + expirationStartTimestamp: setExpirationStartTimestamp( + expirationType, + expirationType === 'deleteAfterSend' ? networkTimestamp : undefined + ), }); await buildAnswerAndSendIt(fromSender); @@ -1167,13 +1181,21 @@ async function addMissedCallMessage(callerPubkey: string, sentAt: number) { incomingCallConversation.set('active_at', getNowWithNetworkOffset()); } + const expirationType = incomingCallConversation.get('expirationType'); await incomingCallConversation?.addSingleIncomingMessage({ + callNotificationType: 'missed-call', source: callerPubkey, sent_at: sentAt, received_at: getNowWithNetworkOffset(), - expireTimer: 0, - callNotificationType: 'missed-call', unread: 1, + expirationType: expirationType !== 'off' ? expirationType : undefined, + expireTimer: incomingCallConversation.get('expireTimer') + ? incomingCallConversation.get('expireTimer') + : 0, + expirationStartTimestamp: setExpirationStartTimestamp( + expirationType, + expirationType === 'deleteAfterSend' ? sentAt : undefined + ), }); } diff --git a/ts/state/ducks/conversations.ts b/ts/state/ducks/conversations.ts index 826342494..053b7ef58 100644 --- a/ts/state/ducks/conversations.ts +++ b/ts/state/ducks/conversations.ts @@ -22,12 +22,12 @@ import { } from '../../util/expiringMessages'; export type CallNotificationType = 'missed-call' | 'started-call' | 'answered-a-call'; -export type PropsForCallNotification = { + +export interface PropsForCallNotification extends PropsForExpiringMessage { notificationType: CallNotificationType; - messageId: string; receivedAt: number; isUnread: boolean; -}; +} export type MessageModelPropsWithoutConvoProps = { propsForMessage: PropsForMessageWithoutConvoProps; @@ -78,6 +78,7 @@ export type FindAndFormatContactType = { export type PropsForExpiringMessage = { convoId?: string; messageId: string; + direction: MessageModelType; expirationTimestamp?: number | null; expirationLength?: number | null; isExpired?: boolean; @@ -96,7 +97,6 @@ export interface PropsForExpirationTimer extends PropsForExpiringMessage { messageId: string; isUnread: boolean; receivedAt: number | undefined; - direction: MessageModelType; } export type PropsForGroupUpdateGeneral = { @@ -140,7 +140,6 @@ export type PropsForGroupUpdate = { export interface PropsForGroupInvitation extends PropsForExpiringMessage { serverName: string; url: string; - direction: MessageModelType; acceptUrl: string; messageId: string; receivedAt?: number; diff --git a/ts/util/expiringMessages.ts b/ts/util/expiringMessages.ts index a88fd885e..71c93a33e 100644 --- a/ts/util/expiringMessages.ts +++ b/ts/util/expiringMessages.ts @@ -7,7 +7,6 @@ import { initWallClockListener } from './wallClockListener'; import { Data } from '../data/data'; import { getConversationController } from '../session/conversations'; -import { MessageModel } from '../models/message'; import { getNowWithNetworkOffset } from '../session/apis/snode_api/SNodeAPI'; // TODO Might need to be improved by using an enum @@ -198,16 +197,10 @@ export const ExpirationTimerOptions = { }; export function setExpirationStartTimestamp( - message: MessageModel, mode: DisappearingMessageType, timestamp?: number -): MessageModel | null { - if (message.get('expirationStartTimestamp') > 0) { - window.log.info(`WIP: Expiration Timer already set. Ignoring new value.`); - return null; - } - - let expirationStartTimestamp = getNowWithNetworkOffset(); +): number | undefined { + let expirationStartTimestamp: number | undefined = getNowWithNetworkOffset(); if (timestamp) { window.log.info( @@ -221,26 +214,25 @@ export function setExpirationStartTimestamp( expirationStartTimestamp = Math.min(expirationStartTimestamp, timestamp); } - message.set('expirationStartTimestamp', expirationStartTimestamp); - if (mode === 'deleteAfterRead') { window.log.info( `WIP: We set the start timestamp for a delete after read message to ${new Date( expirationStartTimestamp - ).toLocaleTimeString()}`, - message + ).toLocaleTimeString()}` ); } else if (mode === 'deleteAfterSend') { window.log.info( `WIP: We set the start timestamp for a delete after send message to ${new Date( expirationStartTimestamp - ).toLocaleTimeString()}`, - message + ).toLocaleTimeString()}` ); + } else if (mode === 'off') { + window.log.info(`WIP: Disappearing message mode "${mode}" set. We can safely ignore this.`); + expirationStartTimestamp = undefined; } else { - console.log(`WIP: Invalid disappearing message mode "${mode}" set. Ignoring`); - return null; + window.log.info(`WIP: Invalid disappearing message mode "${mode}" set. Ignoring`); + expirationStartTimestamp = undefined; } - return message; + return expirationStartTimestamp; }