send dataExtractionNotificaionMessage on saving attachment on priv chats

pull/1672/head
Audric Ackermann 3 years ago
parent b87faa9291
commit 810ccdf675
No known key found for this signature in database
GPG Key ID: 999F434D76324AD4

@ -37,6 +37,8 @@ message Content {
optional ReceiptMessage receiptMessage = 5;
optional TypingMessage typingMessage = 6;
optional ConfigurationMessage configurationMessage = 7;
optional DataExtractionNotification dataExtractionNotification = 82;
}
message KeyPair {
@ -46,6 +48,17 @@ message KeyPair {
required bytes privateKey = 2;
}
message DataExtractionNotification {
enum Type {
SCREENSHOT = 1; // no way to know this on Desktop
MEDIA_SAVED = 2; // timestamp
}
// @required
required Type type = 1;
optional uint64 timestamp = 2;
}
message DataMessage {

@ -18,12 +18,20 @@ export interface MediaItemType {
index: number;
attachment: AttachmentType;
message: Message;
messageTimestamp: number;
messageSender: string;
}
type Props = {
close: () => void;
media: Array<MediaItemType>;
onSave?: (options: { attachment: AttachmentType; message: Message; index: number }) => void;
onSave?: (options: {
attachment: AttachmentType;
message: Message;
index: number;
messageTimestamp?: number;
messageSender: string;
}) => void;
selectedIndex: number;
};
@ -58,11 +66,12 @@ export const LightboxGallery = (props: Props) => {
}
const mediaItem = media[currentIndex];
onSave({
attachment: mediaItem.attachment,
message: mediaItem.message,
index: mediaItem.index,
messageTimestamp: mediaItem.messageTimestamp || mediaItem?.message?.sent_at,
messageSender: mediaItem.messageSender || (mediaItem?.message as any)?.source,
});
};

@ -4,4 +4,5 @@ export type Message = {
id: string;
attachments: Array<Attachment>;
received_at: number;
sent_at: number;
};

@ -32,6 +32,7 @@ import { getDecryptedMediaUrl } from '../../../session/crypto/DecryptedAttachmen
import { deleteOpenGroupMessages } from '../../../interactions/conversation';
import { ConversationTypeEnum } from '../../../models/conversation';
import { updateMentionsMembers } from '../../../state/ducks/mentionsInput';
import { sendDataExtractionNotification } from '../../../session/messages/outgoing/controlMessage/DataExtractionNotificationMessage';
interface State {
// Message sending progress
@ -63,7 +64,12 @@ interface State {
quotedMessageProps?: any;
// lightbox options
lightBoxOptions?: any;
lightBoxOptions?: LightBoxOptions;
}
export interface LightBoxOptions {
media: Array<MediaItemType>;
attachment: any;
}
interface Props {
@ -520,7 +526,7 @@ export class SessionConversation extends React.Component<Props, State> {
onRemoveModerators: () => {
window.Whisper.events.trigger('removeModerators', conversation);
},
onShowLightBox: (lightBoxOptions = {}) => {
onShowLightBox: (lightBoxOptions?: LightBoxOptions) => {
this.setState({ lightBoxOptions });
},
};
@ -760,14 +766,18 @@ export class SessionConversation extends React.Component<Props, State> {
}
private onClickAttachment(attachment: any, message: any) {
// message is MessageTypeInConvo.propsForMessage I think
const media = (message.attachments || []).map((attachmentForMedia: any) => {
return {
objectURL: attachmentForMedia.url,
contentType: attachmentForMedia.contentType,
attachment: attachmentForMedia,
messageSender: message.authorPhoneNumber,
messageTimestamp: message.direction !== 'outgoing' ? message.timestamp : undefined, // do not set this field when the message was sent from us
// if it is set, this will trigger a sending of DataExtractionNotification to that user, but for an attachment we sent ourself.
};
});
const lightBoxOptions = {
const lightBoxOptions: LightBoxOptions = {
media,
attachment,
};
@ -858,7 +868,7 @@ export class SessionConversation extends React.Component<Props, State> {
});
}
private renderLightBox({ media, attachment }: { media: Array<MediaItemType>; attachment: any }) {
private renderLightBox({ media, attachment }: LightBoxOptions) {
const selectedIndex =
media.length > 1
? media.findIndex((mediaMessage: any) => mediaMessage.attachment.path === attachment.path)
@ -880,10 +890,14 @@ export class SessionConversation extends React.Component<Props, State> {
attachment,
message,
index,
messageTimestamp,
messageSender,
}: {
attachment: AttachmentType;
message?: Message;
index?: number;
messageTimestamp?: number;
messageSender: string;
}) {
const { getAbsoluteAttachmentPath } = window.Signal.Migrations;
attachment.url = await getDecryptedMediaUrl(attachment.url, attachment.contentType);
@ -891,8 +905,14 @@ export class SessionConversation extends React.Component<Props, State> {
attachment,
document,
getAbsolutePath: getAbsoluteAttachmentPath,
timestamp: message?.received_at || Date.now(),
timestamp: messageTimestamp || message?.received_at,
});
await sendDataExtractionNotification(
this.props.selectedConversationKey,
messageSender,
messageTimestamp
);
}
private async onChoseAttachments(attachmentsFileList: Array<File>) {

@ -45,7 +45,14 @@ interface Props {
replyToMessage: (messageId: number) => Promise<void>;
showMessageDetails: (messageProps: any) => void;
onClickAttachment: (attachment: any, message: any) => void;
onDownloadAttachment: ({ attachment }: { attachment: any }) => void;
onDownloadAttachment: ({
attachment,
messageTimestamp,
}: {
attachment: any;
messageTimestamp: number;
messageSender: string;
}) => void;
onDeleteSelectedMessages: () => Promise<void>;
}
@ -305,7 +312,11 @@ export class SessionMessagesList extends React.Component<Props, State> {
this.props.onClickAttachment(attachment, messageProps);
};
messageProps.onDownload = (attachment: AttachmentType) => {
this.props.onDownloadAttachment({ attachment });
this.props.onDownloadAttachment({
attachment,
messageTimestamp: messageProps.timestamp,
messageSender: messageProps.authorPhoneNumber,
});
};
messageProps.isQuotedMessageToAnimate = messageProps.id === this.state.animateQuotedMessageId;

@ -18,6 +18,9 @@ import {
getMessagesWithVisualMediaAttachments,
} from '../../../data/data';
import { getDecryptedMediaUrl } from '../../../session/crypto/DecryptedAttachmentsManager';
import { LightBoxOptions } from './SessionConversation';
import { UserUtils } from '../../../session/utils';
import { sendDataExtractionNotification } from '../../../session/messages/outgoing/controlMessage/DataExtractionNotificationMessage';
interface Props {
id: string;
@ -44,7 +47,7 @@ interface Props {
onAddModerators: () => void;
onRemoveModerators: () => void;
onUpdateGroupMembers: () => void;
onShowLightBox: (options: any) => void;
onShowLightBox: (lightboxOptions?: LightBoxOptions) => void;
onSetDisappearingMessages: (seconds: number) => void;
theme: DefaultTheme;
}
@ -169,7 +172,7 @@ class SessionRightPanel extends React.Component<Props, State> {
});
const saveAttachment = async ({ attachment, message }: any = {}) => {
const timestamp = message.received_at;
const timestamp = message.received_at as number | undefined;
attachment.url = await getDecryptedMediaUrl(attachment.url, attachment.contentType);
save({
attachment,
@ -177,6 +180,7 @@ class SessionRightPanel extends React.Component<Props, State> {
getAbsolutePath: window.Signal.Migrations.getAbsoluteAttachmentPath,
timestamp,
});
await sendDataExtractionNotification(this.props.id, message?.source, timestamp);
};
const onItemClick = ({ message, attachment, type }: any) => {
@ -187,11 +191,17 @@ class SessionRightPanel extends React.Component<Props, State> {
}
case 'media': {
// don't set the messageTimestamp when we are the sender, so we don't trigger a notification when
// we save the same attachment we sent ourself to another user
const messageTimestamp =
message.source !== UserUtils.getOurPubKeyStrFromCache()
? message.sent_at || message.received_at
: undefined;
const lightBoxOptions = {
media,
attachment,
message,
};
messageTimestamp,
} as LightBoxOptions;
this.onShowLightBox(lightBoxOptions);
break;
}
@ -208,8 +218,8 @@ class SessionRightPanel extends React.Component<Props, State> {
};
}
public onShowLightBox(options: any) {
this.props.onShowLightBox(options);
public onShowLightBox(lightboxOptions: LightBoxOptions) {
this.props.onShowLightBox(lightboxOptions);
}
// tslint:disable-next-line: cyclomatic-complexity

@ -30,9 +30,9 @@ import {
uploadQuoteThumbnailsV2,
} from '../session/utils/AttachmentsV2';
import { acceptOpenGroupInvitation } from '../interactions/message';
import { OpenGroupMessageV2 } from '../opengroup/opengroupV2/OpenGroupMessageV2';
import { OpenGroupVisibleMessage } from '../session/messages/outgoing/visibleMessage/OpenGroupVisibleMessage';
import { getV2OpenGroupRoom } from '../data/opengroups';
export class MessageModel extends Backbone.Model<MessageAttributes> {
public propsForTimerNotification: any;
public propsForGroupNotification: any;

@ -342,7 +342,7 @@ export async function innerHandleContentMessage(
return;
}
if (content.typingMessage) {
await handleTypingMessage(envelope, content.typingMessage);
await handleTypingMessage(envelope, content.typingMessage as SignalService.TypingMessage);
return;
}
if (content.configurationMessage) {
@ -353,6 +353,14 @@ export async function innerHandleContentMessage(
);
return;
}
if (content.dataExtractionNotification) {
window?.log?.warn('content.dataExtractionNotification', content.dataExtractionNotification);
void handleDataExtractionNotification(
envelope,
content.dataExtractionNotification as SignalService.DataExtractionNotification
);
return;
}
} catch (e) {
window?.log?.warn(e);
}
@ -422,17 +430,18 @@ async function handleReceiptMessage(
async function handleTypingMessage(
envelope: EnvelopePlus,
iTypingMessage: SignalService.ITypingMessage
typingMessage: SignalService.TypingMessage
): Promise<void> {
const ev = new Event('typing');
const typingMessage = iTypingMessage as SignalService.TypingMessage;
const { timestamp, action } = typingMessage;
const { source } = envelope;
await removeFromCache(envelope);
// We don't do anything with incoming typing messages if the setting is disabled
if (!window.storage.get('typing-indicators-setting')) {
return;
}
if (envelope.timestamp && timestamp) {
const envelopeTimestamp = Lodash.toNumber(envelope.timestamp);
const typingTimestamp = Lodash.toNumber(timestamp);
@ -445,11 +454,6 @@ async function handleTypingMessage(
}
}
// We don't do anything with incoming typing messages if the setting is disabled
if (!window.storage.get('typing-indicators-setting')) {
return;
}
// typing message are only working with direct chats/ not groups
const conversation = ConversationController.getInstance().get(source);

@ -2,7 +2,6 @@
import { SignalService } from '../../../../protobuf';
import { MessageParams } from '../Message';
import { Constants } from '../../..';
import { ECKeyPair } from '../../../../receiver/keypairs';
import { fromHexToArray } from '../../../utils/String';
import { PubKey } from '../../../types';

@ -0,0 +1,79 @@
import { SignalService } from '../../../../protobuf';
import { MessageParams } from '../Message';
import { ContentMessage } from '..';
import { v4 as uuid } from 'uuid';
import { PubKey } from '../../../types';
import { getMessageQueue } from '../../..';
import { ConversationController } from '../../../conversations';
import { UserUtils } from '../../../utils';
interface DataExtractionNotificationMessageParams extends MessageParams {
referencedAttachmentTimestamp: number;
}
export class DataExtractionNotificationMessage extends ContentMessage {
public readonly referencedAttachmentTimestamp: number;
constructor(params: DataExtractionNotificationMessageParams) {
super({ timestamp: params.timestamp, identifier: params.identifier });
this.referencedAttachmentTimestamp = params.referencedAttachmentTimestamp;
// this does not make any sense
if (!this.referencedAttachmentTimestamp) {
throw new Error('referencedAttachmentTimestamp must be set');
}
}
public contentProto(): SignalService.Content {
return new SignalService.Content({
dataExtractionNotification: this.dataExtractionProto(),
});
}
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
const dataExtraction = new SignalService.DataExtractionNotification();
dataExtraction.type = action;
dataExtraction.timestamp = this.referencedAttachmentTimestamp;
return dataExtraction;
}
}
/**
* Currently only enabled for private chats
*/
export const sendDataExtractionNotification = async (
conversationId: string,
attachmentSender: string,
referencedAttachmentTimestamp?: number
) => {
const convo = ConversationController.getInstance().get(conversationId);
if (
!convo ||
!convo.isPrivate() ||
convo.isMe() ||
UserUtils.isUsFromCache(PubKey.cast(attachmentSender)) ||
!referencedAttachmentTimestamp
) {
window.log.warn('Not sending saving attachment notification for', attachmentSender);
return;
}
const dataExtractionNotificationMessage = new DataExtractionNotificationMessage({
referencedAttachmentTimestamp,
identifier: uuid(),
timestamp: Date.now(),
});
const pubkey = PubKey.cast(conversationId);
window.log.info(
`Sending DataExtractionNotification to ${conversationId} about attachment: ${referencedAttachmentTimestamp}`
);
try {
await getMessageQueue().sendToPubKey(pubkey, dataExtractionNotificationMessage);
} catch (e) {
window.log.warn('failed to send data extraction notification', e);
}
};

@ -7,6 +7,10 @@ import {
Section,
} from '../../../components/conversation/media-gallery/groupMediaItemsByDate';
import { MediaItemType } from '../../../components/LightboxGallery';
import { TestUtils } from '../../test-utils';
const generatedMessageSenderKey = TestUtils.generateFakePubKey().key;
const generatedMessageTimestamp = Date.now();
const toMediaItem = (date: Date): MediaItemType => ({
objectURL: date.toUTCString(),
@ -14,6 +18,7 @@ const toMediaItem = (date: Date): MediaItemType => ({
message: {
id: 'id',
received_at: date.getTime(),
sent_at: date.getTime(),
attachments: [],
},
attachment: {
@ -22,6 +27,8 @@ const toMediaItem = (date: Date): MediaItemType => ({
url: 'url',
},
contentType: IMAGE_JPEG,
messageSender: generatedMessageSenderKey,
messageTimestamp: generatedMessageTimestamp,
});
// tslint:disable: max-func-body-length
@ -57,6 +64,7 @@ describe('groupMediaItemsByDate', () => {
message: {
id: 'id',
sent_at: 1523534400000,
received_at: 1523534400000,
attachments: [],
},
@ -65,6 +73,8 @@ describe('groupMediaItemsByDate', () => {
contentType: IMAGE_JPEG,
url: 'url',
},
messageSender: generatedMessageSenderKey,
messageTimestamp: generatedMessageTimestamp,
},
{
objectURL: 'Thu, 12 Apr 2018 00:01:00 GMT',
@ -73,6 +83,7 @@ describe('groupMediaItemsByDate', () => {
message: {
id: 'id',
received_at: 1523491260000,
sent_at: 1523491260000,
attachments: [],
},
attachment: {
@ -80,6 +91,8 @@ describe('groupMediaItemsByDate', () => {
contentType: IMAGE_JPEG,
url: 'url',
},
messageSender: generatedMessageSenderKey,
messageTimestamp: generatedMessageTimestamp,
},
],
},
@ -93,6 +106,8 @@ describe('groupMediaItemsByDate', () => {
message: {
id: 'id',
received_at: 1523491140000,
sent_at: 1523491140000,
attachments: [],
},
attachment: {
@ -100,6 +115,8 @@ describe('groupMediaItemsByDate', () => {
contentType: IMAGE_JPEG,
url: 'url',
},
messageSender: generatedMessageSenderKey,
messageTimestamp: generatedMessageTimestamp,
},
],
},
@ -113,6 +130,8 @@ describe('groupMediaItemsByDate', () => {
message: {
id: 'id',
received_at: 1523232060000,
sent_at: 1523232060000,
attachments: [],
},
attachment: {
@ -120,6 +139,8 @@ describe('groupMediaItemsByDate', () => {
contentType: IMAGE_JPEG,
url: 'url',
},
messageSender: generatedMessageSenderKey,
messageTimestamp: generatedMessageTimestamp,
},
],
},
@ -133,6 +154,8 @@ describe('groupMediaItemsByDate', () => {
message: {
id: 'id',
received_at: 1523231940000,
sent_at: 1523231940000,
attachments: [],
},
attachment: {
@ -140,6 +163,8 @@ describe('groupMediaItemsByDate', () => {
contentType: IMAGE_JPEG,
url: 'url',
},
messageSender: generatedMessageSenderKey,
messageTimestamp: generatedMessageTimestamp,
},
{
objectURL: 'Sun, 01 Apr 2018 00:01:00 GMT',
@ -147,6 +172,8 @@ describe('groupMediaItemsByDate', () => {
message: {
id: 'id',
received_at: 1522540860000,
sent_at: 1522540860000,
attachments: [],
},
contentType: IMAGE_JPEG,
@ -155,6 +182,8 @@ describe('groupMediaItemsByDate', () => {
contentType: IMAGE_JPEG,
url: 'url',
},
messageSender: generatedMessageSenderKey,
messageTimestamp: generatedMessageTimestamp,
},
],
},
@ -170,6 +199,8 @@ describe('groupMediaItemsByDate', () => {
message: {
id: 'id',
received_at: 1522540740000,
sent_at: 1522540740000,
attachments: [],
},
attachment: {
@ -177,6 +208,8 @@ describe('groupMediaItemsByDate', () => {
contentType: IMAGE_JPEG,
url: 'url',
},
messageSender: generatedMessageSenderKey,
messageTimestamp: generatedMessageTimestamp,
},
{
objectURL: 'Thu, 01 Mar 2018 14:00:00 GMT',
@ -185,6 +218,8 @@ describe('groupMediaItemsByDate', () => {
message: {
id: 'id',
received_at: 1519912800000,
sent_at: 1519912800000,
attachments: [],
},
attachment: {
@ -192,6 +227,8 @@ describe('groupMediaItemsByDate', () => {
contentType: IMAGE_JPEG,
url: 'url',
},
messageSender: generatedMessageSenderKey,
messageTimestamp: generatedMessageTimestamp,
},
],
},
@ -206,6 +243,8 @@ describe('groupMediaItemsByDate', () => {
message: {
id: 'id',
received_at: 1298937540000,
sent_at: 1298937540000,
attachments: [],
},
attachment: {
@ -214,6 +253,8 @@ describe('groupMediaItemsByDate', () => {
url: 'url',
},
contentType: IMAGE_JPEG,
messageSender: generatedMessageSenderKey,
messageTimestamp: generatedMessageTimestamp,
},
{
objectURL: 'Tue, 01 Feb 2011 10:00:00 GMT',
@ -222,8 +263,12 @@ describe('groupMediaItemsByDate', () => {
message: {
id: 'id',
received_at: 1296554400000,
sent_at: 1296554400000,
attachments: [],
},
messageSender: generatedMessageSenderKey,
messageTimestamp: generatedMessageTimestamp,
attachment: {
fileName: 'fileName',
contentType: IMAGE_JPEG,

Loading…
Cancel
Save