import { getMessageQueue } from '../../..'; import { SignalService } from '../../../../protobuf'; import { SnodeNamespaces } from '../../../apis/snode_api/namespaces'; import { getConversationController } from '../../../conversations'; import { DisappearingMessages } from '../../../disappearing_messages'; import { PubKey } from '../../../types'; import { UserUtils } from '../../../utils'; import { ExpirableMessage, ExpirableMessageParams } from '../ExpirableMessage'; interface DataExtractionNotificationMessageParams extends ExpirableMessageParams { referencedAttachmentTimestamp: number; } export class DataExtractionNotificationMessage extends ExpirableMessage { public readonly referencedAttachmentTimestamp: number; constructor(params: DataExtractionNotificationMessageParams) { super(params); this.referencedAttachmentTimestamp = params.referencedAttachmentTimestamp; // this does not make any sense if (!this.referencedAttachmentTimestamp) { throw new Error('referencedAttachmentTimestamp must be set'); } } public contentProto(): SignalService.Content { const content = super.contentProto(); content.dataExtractionNotification = this.dataExtractionProto(); return content; } protected dataExtractionProto(): SignalService.DataExtractionNotification { const ACTION_ENUM = SignalService.DataExtractionNotification.Type; const action = ACTION_ENUM.MEDIA_SAVED; // we cannot know when user screenshots, so it can only be a media saved on desktop return new SignalService.DataExtractionNotification({ type: action, timestamp: this.referencedAttachmentTimestamp, }); } } /** * Currently only enabled for private chats */ export const sendDataExtractionNotification = async ( conversationId: string, attachmentSender: string, referencedAttachmentTimestamp: number ) => { const convo = getConversationController().get(conversationId); if (!convo || !convo.isPrivate() || convo.isMe() || UserUtils.isUsFromCache(attachmentSender)) { window.log.warn('Not sending saving attachment notification for', attachmentSender); return; } const { expirationType, expireTimer } = DisappearingMessages.forcedDeleteAfterReadMsgSetting( convo ); // DataExtractionNotification are expiring with a forced DaR timer if a DaS is set. // It's because we want the DataExtractionNotification to stay in the swarm as much as possible, // but also expire on the recipient's side (and synced) once read. const dataExtractionNotificationMessage = new DataExtractionNotificationMessage({ referencedAttachmentTimestamp, timestamp: Date.now(), expirationType, expireTimer, }); const pubkey = PubKey.cast(conversationId); window.log.info( `Sending DataExtractionNotification to ${conversationId} about attachment: ${referencedAttachmentTimestamp}` ); try { await getMessageQueue().sendToPubKey( pubkey, dataExtractionNotificationMessage, SnodeNamespaces.UserMessages ); } catch (e) { window.log.warn('failed to send data extraction notification', e); } };