do not load right panel data unless it is visibl

pull/1783/head
Audric Ackermann 4 years ago
parent f0db797a9a
commit c8aa73626e
No known key found for this signature in database
GPG Key ID: 999F434D76324AD4

@ -35,9 +35,9 @@ window.semver = semver;
window.platform = process.platform; window.platform = process.platform;
window.getTitle = () => title; window.getTitle = () => title;
window.getEnvironment = () => config.environment; window.getEnvironment = () => config.environment;
window.isDev = () => config.environment === 'development';
window.getAppInstance = () => config.appInstance; window.getAppInstance = () => config.appInstance;
window.getVersion = () => config.version; window.getVersion = () => config.version;
window.isDev = () => config.environment === 'development';
window.getExpiration = () => config.buildExpiration; window.getExpiration = () => config.buildExpiration;
window.getCommitHash = () => config.commitHash; window.getCommitHash = () => config.commitHash;
window.getNodeVersion = () => config.node_version; window.getNodeVersion = () => config.node_version;

@ -8,6 +8,7 @@ import { AttachmentType } from '../types/Attachment';
import { SessionInput } from './session/SessionInput'; import { SessionInput } from './session/SessionInput';
import { SessionButton, SessionButtonColor, SessionButtonType } from './session/SessionButton'; import { SessionButton, SessionButtonColor, SessionButtonType } from './session/SessionButton';
import { darkTheme, lightTheme } from '../state/ducks/SessionTheme'; import { darkTheme, lightTheme } from '../state/ducks/SessionTheme';
import autoBind from 'auto-bind';
interface Props { interface Props {
attachment: AttachmentType; attachment: AttachmentType;
@ -31,8 +32,7 @@ export class CaptionEditor extends React.Component<Props, State> {
this.state = { this.state = {
caption: caption || '', caption: caption || '',
}; };
this.onSave = this.onSave.bind(this); autoBind(this);
this.onChange = this.onChange.bind(this);
this.inputRef = React.createRef(); this.inputRef = React.createRef();
} }

@ -5,9 +5,8 @@ import React, { useEffect, useState } from 'react';
import * as MIME from '../types/MIME'; import * as MIME from '../types/MIME';
import { Lightbox } from './Lightbox'; import { Lightbox } from './Lightbox';
import { Message } from './conversation/media-gallery/types/Message';
import { AttachmentType } from '../types/Attachment'; import { AttachmentTypeWithPath } from '../types/Attachment';
// tslint:disable-next-line: no-submodule-imports // tslint:disable-next-line: no-submodule-imports
import useKey from 'react-use/lib/useKey'; import useKey from 'react-use/lib/useKey';
@ -16,22 +15,16 @@ export interface MediaItemType {
thumbnailObjectUrl?: string; thumbnailObjectUrl?: string;
contentType: MIME.MIMEType; contentType: MIME.MIMEType;
index: number; index: number;
attachment: AttachmentType; attachment: AttachmentTypeWithPath;
message: Message;
messageTimestamp: number; messageTimestamp: number;
messageSender: string; messageSender: string;
messageId: string;
} }
type Props = { type Props = {
close: () => void; close: () => void;
media: Array<MediaItemType>; media: Array<MediaItemType>;
onSave?: (options: { onSave?: (saveData: MediaItemType) => void;
attachment: AttachmentType;
message: Message;
index: number;
messageTimestamp?: number;
messageSender: string;
}) => void;
selectedIndex: number; selectedIndex: number;
}; };
@ -66,16 +59,10 @@ export const LightboxGallery = (props: Props) => {
} }
const mediaItem = media[currentIndex]; const mediaItem = media[currentIndex];
onSave({ onSave(mediaItem);
attachment: mediaItem.attachment,
message: mediaItem.message,
index: mediaItem.index,
messageTimestamp: mediaItem.messageTimestamp || mediaItem?.message?.sent_at,
messageSender: mediaItem.messageSender || (mediaItem?.message as any)?.source,
});
}; };
const objectURL = selectedMedia.objectURL || 'images/alert-outline.svg'; const objectURL = selectedMedia?.objectURL || 'images/alert-outline.svg';
const { attachment } = selectedMedia; const { attachment } = selectedMedia;
const saveCallback = onSave ? handleSave : undefined; const saveCallback = onSave ? handleSave : undefined;

@ -66,7 +66,7 @@ const OnionPathModalInner = () => {
<OnionNodeStatusLight <OnionNodeStatusLight
glowDuration={glowDuration} glowDuration={glowDuration}
glowStartDelay={index} glowStartDelay={index}
key={index} key={`light-${index}`}
/> />
); );
})} })}
@ -80,7 +80,11 @@ const OnionPathModalInner = () => {
if (!labelText) { if (!labelText) {
labelText = window.i18n('unknownCountry'); labelText = window.i18n('unknownCountry');
} }
return labelText ? <div className="onion__node__country">{labelText}</div> : null; return labelText ? (
<div className="onion__node__country" key={`country-${index}`}>
{labelText}
</div>
) : null;
})} })}
</Flex> </Flex>
</Flex> </Flex>

@ -30,23 +30,23 @@ export class AttachmentSection extends React.Component<Props> {
return mediaItems.map((mediaItem, position, array) => { return mediaItems.map((mediaItem, position, array) => {
const shouldShowSeparator = position < array.length - 1; const shouldShowSeparator = position < array.length - 1;
const { message, index, attachment } = mediaItem; const { index, attachment, messageTimestamp, messageId } = mediaItem;
const onClick = this.createClickHandler(mediaItem); const onClick = this.createClickHandler(mediaItem);
switch (type) { switch (type) {
case 'media': case 'media':
return ( return (
<MediaGridItem key={`${message.id}-${index}`} mediaItem={mediaItem} onClick={onClick} /> <MediaGridItem key={`${messageId}-${index}`} mediaItem={mediaItem} onClick={onClick} />
); );
case 'documents': case 'documents':
return ( return (
<DocumentListItem <DocumentListItem
key={`${message.id}-${index}`} key={`${messageId}-${index}`}
fileName={attachment.fileName} fileName={attachment.fileName}
fileSize={attachment.size} fileSize={attachment.size}
shouldShowSeparator={shouldShowSeparator} shouldShowSeparator={shouldShowSeparator}
onClick={onClick} onClick={onClick}
timestamp={message.received_at} timestamp={messageTimestamp}
/> />
); );
default: default:
@ -57,12 +57,11 @@ export class AttachmentSection extends React.Component<Props> {
private readonly createClickHandler = (mediaItem: MediaItemType) => () => { private readonly createClickHandler = (mediaItem: MediaItemType) => () => {
const { onItemClick, type } = this.props; const { onItemClick, type } = this.props;
const { message, attachment } = mediaItem;
if (!onItemClick) { if (!onItemClick) {
return; return;
} }
onItemClick({ type, message, attachment }); onItemClick({ mediaItem, type });
}; };
} }

@ -5,46 +5,30 @@ import moment from 'moment';
// tslint:disable-next-line:match-default-export-name // tslint:disable-next-line:match-default-export-name
import formatFileSize from 'filesize'; import formatFileSize from 'filesize';
interface Props { type Props = {
// Required // Required
timestamp: number; timestamp: number;
// Optional // Optional
fileName?: string; fileName?: string;
fileSize?: number; fileSize?: number | null;
onClick?: () => void; onClick?: () => void;
shouldShowSeparator?: boolean; shouldShowSeparator?: boolean;
} };
export class DocumentListItem extends React.Component<Props> { export const DocumentListItem = (props: Props) => {
public static defaultProps: Partial<Props> = { const { shouldShowSeparator, fileName, fileSize, timestamp } = props;
shouldShowSeparator: true,
};
public render() { const defaultShowSeparator = shouldShowSeparator === undefined ? true : shouldShowSeparator;
const { shouldShowSeparator } = this.props;
return ( return (
<div <div
className={classNames( className={classNames(
'module-document-list-item', 'module-document-list-item',
shouldShowSeparator ? 'module-document-list-item--with-separator' : null defaultShowSeparator ? 'module-document-list-item--with-separator' : null
)} )}
> >
{this.renderContent()} <div className="module-document-list-item__content" role="button" onClick={props.onClick}>
</div>
);
}
private renderContent() {
const { fileName, fileSize, timestamp } = this.props;
return (
<div
className="module-document-list-item__content"
role="button"
onClick={this.props.onClick}
>
<div className="module-document-list-item__icon" /> <div className="module-document-list-item__icon" />
<div className="module-document-list-item__metadata"> <div className="module-document-list-item__metadata">
<span className="module-document-list-item__file-name">{fileName}</span> <span className="module-document-list-item__file-name">{fileName}</span>
@ -56,6 +40,6 @@ export class DocumentListItem extends React.Component<Props> {
{moment(timestamp).format('ddd, MMM D, Y')} {moment(timestamp).format('ddd, MMM D, Y')}
</div> </div>
</div> </div>
); </div>
} );
} };

@ -25,9 +25,9 @@ export const groupMediaItemsByDate = (
const referenceDateTime = moment.utc(timestamp); const referenceDateTime = moment.utc(timestamp);
const sortedMediaItem = sortBy(mediaItems, mediaItem => { const sortedMediaItem = sortBy(mediaItems, mediaItem => {
const { message } = mediaItem; const { messageTimestamp } = mediaItem;
return -message.received_at; return -messageTimestamp;
}); });
const messagesWithSection = sortedMediaItem.map(withSection(referenceDateTime)); const messagesWithSection = sortedMediaItem.map(withSection(referenceDateTime));
const groupedMediaItem = groupBy(messagesWithSection, 'type'); const groupedMediaItem = groupBy(messagesWithSection, 'type');
@ -102,8 +102,8 @@ const withSection = (referenceDateTime: moment.Moment) => (
const thisWeek = moment(referenceDateTime).startOf('isoWeek'); const thisWeek = moment(referenceDateTime).startOf('isoWeek');
const thisMonth = moment(referenceDateTime).startOf('month'); const thisMonth = moment(referenceDateTime).startOf('month');
const { message } = mediaItem; const { messageTimestamp } = mediaItem;
const mediaItemReceivedDate = moment.utc(message.received_at); const mediaItemReceivedDate = moment.utc(messageTimestamp);
if (mediaItemReceivedDate.isAfter(today)) { if (mediaItemReceivedDate.isAfter(today)) {
return { return {
order: 0, order: 0,

@ -1,8 +1,6 @@
import { AttachmentType } from '../../../../types/Attachment'; import { MediaItemType } from '../../../LightboxGallery';
import { Message } from './Message';
export interface ItemClickEvent { export interface ItemClickEvent {
message: Message; mediaItem: MediaItemType;
attachment: AttachmentType;
type: 'media' | 'documents'; type: 'media' | 'documents';
} }

@ -592,6 +592,9 @@ export class SessionCompositionBox extends React.Component<Props, State> {
...ret.image, ...ret.image,
url: URL.createObjectURL(blob), url: URL.createObjectURL(blob),
fileName: 'preview', fileName: 'preview',
fileSize: null,
screenshot: null,
thumbnail: null,
}; };
image = imageAttachment; image = imageAttachment;
} }

@ -17,11 +17,11 @@ import { SessionMessagesList } from './SessionMessagesList';
import { LightboxGallery, MediaItemType } from '../../LightboxGallery'; import { LightboxGallery, MediaItemType } from '../../LightboxGallery';
import { Message } from '../../conversation/media-gallery/types/Message'; import { Message } from '../../conversation/media-gallery/types/Message';
import { AttachmentType, save } from '../../../types/Attachment'; import { AttachmentType, AttachmentTypeWithPath, save } from '../../../types/Attachment';
import { ToastUtils, UserUtils } from '../../../session/utils'; import { ToastUtils, UserUtils } from '../../../session/utils';
import * as MIME from '../../../types/MIME'; import * as MIME from '../../../types/MIME';
import { SessionFileDropzone } from './SessionFileDropzone'; import { SessionFileDropzone } from './SessionFileDropzone';
import { ConversationType } from '../../../state/ducks/conversations'; import { ConversationType, PropsForMessage } from '../../../state/ducks/conversations';
import { MessageView } from '../../MainViewController'; import { MessageView } from '../../MainViewController';
import { pushUnblockToSend } from '../../../session/utils/Toast'; import { pushUnblockToSend } from '../../../session/utils/Toast';
import { MessageDetail } from '../../conversation/MessageDetail'; import { MessageDetail } from '../../conversation/MessageDetail';
@ -39,6 +39,7 @@ import { updateMentionsMembers } from '../../../state/ducks/mentionsInput';
import { sendDataExtractionNotification } from '../../../session/messages/outgoing/controlMessage/DataExtractionNotificationMessage'; import { sendDataExtractionNotification } from '../../../session/messages/outgoing/controlMessage/DataExtractionNotificationMessage';
import { SessionButtonColor } from '../SessionButton'; import { SessionButtonColor } from '../SessionButton';
import { usingClosedConversationDetails } from '../usingClosedConversationDetails';
interface State { interface State {
// Message sending progress // Message sending progress
messageProgressVisible: boolean; messageProgressVisible: boolean;
@ -74,7 +75,7 @@ interface State {
export interface LightBoxOptions { export interface LightBoxOptions {
media: Array<MediaItemType>; media: Array<MediaItemType>;
attachment: any; attachment: AttachmentTypeWithPath;
} }
interface Props { interface Props {
@ -249,7 +250,6 @@ export class SessionConversation extends React.Component<Props, State> {
return ( return (
<SessionTheme theme={this.props.theme}> <SessionTheme theme={this.props.theme}>
<div className="conversation-header">{this.renderHeader()}</div> <div className="conversation-header">{this.renderHeader()}</div>
{/* <SessionProgress {/* <SessionProgress
visible={this.state.messageProgressVisible} visible={this.state.messageProgressVisible}
value={this.state.sendingProgress} value={this.state.sendingProgress}
@ -257,7 +257,6 @@ export class SessionConversation extends React.Component<Props, State> {
sendStatus={this.state.sendingProgressStatus} sendStatus={this.state.sendingProgressStatus}
resetProgress={this.resetSendingProgress} resetProgress={this.resetSendingProgress}
/> */} /> */}
<div <div
// if you change the classname, also update it on onKeyDown // if you change the classname, also update it on onKeyDown
className={classNames('conversation-content', selectionMode && 'selection-mode')} className={classNames('conversation-content', selectionMode && 'selection-mode')}
@ -306,10 +305,13 @@ export class SessionConversation extends React.Component<Props, State> {
theme={this.props.theme} theme={this.props.theme}
/> />
</div> </div>
<div className={classNames('conversation-item__options-pane', showOptionsPane && 'show')}> <div className={classNames('conversation-item__options-pane', showOptionsPane && 'show')}>
<SessionRightPanelWithDetails {...this.getRightPanelProps()} /> <SessionRightPanelWithDetails
{...this.getRightPanelProps()}
isShowing={showOptionsPane}
/>
</div> </div>
)
</SessionTheme> </SessionTheme>
); );
} }
@ -457,7 +459,7 @@ export class SessionConversation extends React.Component<Props, State> {
phoneNumber: conversation.getNumber(), phoneNumber: conversation.getNumber(),
profileName: conversation.getProfileName(), profileName: conversation.getProfileName(),
avatarPath: conversation.getAvatarPath(), avatarPath: conversation.getAvatarPath(),
isKickedFromGroup: conversation.get('isKickedFromGroup'), isKickedFromGroup: Boolean(conversation.get('isKickedFromGroup')),
left: conversation.get('left'), left: conversation.get('left'),
isGroup: !conversation.isPrivate(), isGroup: !conversation.isPrivate(),
isPublic: conversation.isPublic(), isPublic: conversation.isPublic(),
@ -714,20 +716,25 @@ export class SessionConversation extends React.Component<Props, State> {
}); });
} }
private onClickAttachment(attachment: any, message: any) { private onClickAttachment(attachment: AttachmentTypeWithPath, propsForMessage: PropsForMessage) {
// message is MessageModelProps.propsForMessage I think let index = -1;
const media = (message.attachments || []).map((attachmentForMedia: any) => { const media = (propsForMessage.attachments || []).map(attachmentForMedia => {
index++;
const messageTimestamp =
propsForMessage.timestamp || propsForMessage.serverTimestamp || propsForMessage.receivedAt;
return { return {
objectURL: attachmentForMedia.url, index: _.clone(index),
objectURL: attachmentForMedia.url || undefined,
contentType: attachmentForMedia.contentType, contentType: attachmentForMedia.contentType,
attachment: attachmentForMedia, attachment: attachmentForMedia,
messageSender: message.authorPhoneNumber, messageSender: propsForMessage.authorPhoneNumber,
messageTimestamp: message.direction !== 'outgoing' ? message.timestamp : undefined, // do not set this field when the message was sent from us messageTimestamp,
// if it is set, this will trigger a sending of DataExtractionNotification to that user, but for an attachment we sent ourself. messageId: propsForMessage.id,
}; };
}); });
const lightBoxOptions: LightBoxOptions = { const lightBoxOptions: LightBoxOptions = {
media, media: media as any,
attachment, attachment,
}; };
this.setState({ lightBoxOptions }); this.setState({ lightBoxOptions });
@ -820,8 +827,9 @@ export class SessionConversation extends React.Component<Props, State> {
private renderLightBox({ media, attachment }: LightBoxOptions) { private renderLightBox({ media, attachment }: LightBoxOptions) {
const selectedIndex = const selectedIndex =
media.length > 1 media.length > 1
? media.findIndex((mediaMessage: any) => mediaMessage.attachment.path === attachment.path) ? media.findIndex(mediaMessage => mediaMessage.attachment.path === attachment.path)
: 0; : 0;
console.warn('renderLightBox', { media, attachment });
return ( return (
<LightboxGallery <LightboxGallery
media={media} media={media}
@ -837,15 +845,11 @@ export class SessionConversation extends React.Component<Props, State> {
// THIS DOES NOT DOWNLOAD ANYTHING! it just saves it where the user wants // THIS DOES NOT DOWNLOAD ANYTHING! it just saves it where the user wants
private async saveAttachment({ private async saveAttachment({
attachment, attachment,
message,
index,
messageTimestamp, messageTimestamp,
messageSender, messageSender,
}: { }: {
attachment: AttachmentType; attachment: AttachmentType;
message?: Message; messageTimestamp: number;
index?: number;
messageTimestamp?: number;
messageSender: string; messageSender: string;
}) { }) {
const { getAbsoluteAttachmentPath } = window.Signal.Migrations; const { getAbsoluteAttachmentPath } = window.Signal.Migrations;
@ -854,7 +858,7 @@ export class SessionConversation extends React.Component<Props, State> {
attachment, attachment,
document, document,
getAbsolutePath: getAbsoluteAttachmentPath, getAbsolutePath: getAbsoluteAttachmentPath,
timestamp: messageTimestamp || message?.received_at, timestamp: messageTimestamp,
}); });
await sendDataExtractionNotification( await sendDataExtractionNotification(
@ -937,6 +941,9 @@ export class SessionConversation extends React.Component<Props, State> {
videoUrl: objectUrl, videoUrl: objectUrl,
url, url,
isVoiceMessage: false, isVoiceMessage: false,
fileSize: null,
screenshot: null,
thumbnail: null,
}, },
]); ]);
} catch (error) { } catch (error) {
@ -958,6 +965,9 @@ export class SessionConversation extends React.Component<Props, State> {
contentType, contentType,
url: urlImage, url: urlImage,
isVoiceMessage: false, isVoiceMessage: false,
fileSize: null,
screenshot: null,
thumbnail: null,
}, },
]); ]);
return; return;
@ -973,6 +983,9 @@ export class SessionConversation extends React.Component<Props, State> {
contentType, contentType,
url, url,
isVoiceMessage: false, isVoiceMessage: false,
fileSize: null,
screenshot: null,
thumbnail: null,
}, },
]); ]);
}; };
@ -1017,6 +1030,9 @@ export class SessionConversation extends React.Component<Props, State> {
fileName, fileName,
url: '', url: '',
isVoiceMessage: false, isVoiceMessage: false,
fileSize: null,
screenshot: null,
thumbnail: null,
}, },
]); ]);
} }
@ -1033,6 +1049,9 @@ export class SessionConversation extends React.Component<Props, State> {
fileName, fileName,
isVoiceMessage: false, isVoiceMessage: false,
url: '', url: '',
fileSize: null,
screenshot: null,
thumbnail: null,
}, },
]); ]);
} }

@ -56,7 +56,7 @@ interface Props {
messageTimestamp, messageTimestamp,
}: { }: {
attachment: any; attachment: any;
messageTimestamp?: number; messageTimestamp: number;
messageSender: string; messageSender: string;
}) => void; }) => void;
onDeleteSelectedMessages: () => Promise<void>; onDeleteSelectedMessages: () => Promise<void>;
@ -336,12 +336,17 @@ export class SessionMessagesList extends React.Component<Props, State> {
}; };
regularProps.onClickAttachment = (attachment: AttachmentType) => { regularProps.onClickAttachment = (attachment: AttachmentType) => {
this.props.onClickAttachment(attachment, messageProps); this.props.onClickAttachment(attachment, messageProps.propsForMessage);
}; };
regularProps.onDownload = (attachment: AttachmentType) => { regularProps.onDownload = (attachment: AttachmentType) => {
const messageTimestamp =
messageProps.propsForMessage.timestamp ||
messageProps.propsForMessage.serverTimestamp ||
messageProps.propsForMessage.receivedAt ||
0;
this.props.onDownloadAttachment({ this.props.onDownloadAttachment({
attachment, attachment,
messageTimestamp: messageProps.propsForMessage.timestamp, messageTimestamp,
messageSender: messageProps.propsForMessage.authorPhoneNumber, messageSender: messageProps.propsForMessage.authorPhoneNumber,
}); });
}; };

@ -1,18 +1,18 @@
import React from 'react'; import React, { useEffect, useState } from 'react';
import { SessionIconButton, SessionIconSize, SessionIconType } from '../icon'; import { SessionIconButton, SessionIconSize, SessionIconType } from '../icon';
import { Avatar, AvatarSize } from '../../Avatar'; import { Avatar, AvatarSize } from '../../Avatar';
import { SessionButton, SessionButtonColor, SessionButtonType } from '../SessionButton'; import { SessionButton, SessionButtonColor, SessionButtonType } from '../SessionButton';
import { SessionDropdown } from '../SessionDropdown'; import { SessionDropdown } from '../SessionDropdown';
import { MediaGallery } from '../../conversation/media-gallery/MediaGallery'; import { MediaGallery } from '../../conversation/media-gallery/MediaGallery';
import _ from 'lodash'; import _, { noop } from 'lodash';
import { TimerOption } from '../../conversation/ConversationHeader'; import { TimerOption } from '../../conversation/ConversationHeader';
import { Constants } from '../../../session'; import { Constants } from '../../../session';
import { import {
ConversationAvatar, ConversationAvatar,
usingClosedConversationDetails, usingClosedConversationDetails,
} from '../usingClosedConversationDetails'; } from '../usingClosedConversationDetails';
import { save } from '../../../types/Attachment'; import { AttachmentTypeWithPath, save } from '../../../types/Attachment';
import { DefaultTheme, withTheme } from 'styled-components'; import { DefaultTheme, useTheme, withTheme } from 'styled-components';
import { import {
getMessagesWithFileAttachments, getMessagesWithFileAttachments,
getMessagesWithVisualMediaAttachments, getMessagesWithVisualMediaAttachments,
@ -32,345 +32,203 @@ import {
showUpdateGroupMembersByConvoId, showUpdateGroupMembersByConvoId,
showUpdateGroupNameByConvoId, showUpdateGroupNameByConvoId,
} from '../../../interactions/conversationInteractions'; } from '../../../interactions/conversationInteractions';
import { ItemClickEvent } from '../../conversation/media-gallery/types/ItemClickEvent';
import { MediaItemType } from '../../LightboxGallery';
// tslint:disable-next-line: no-submodule-imports
import useInterval from 'react-use/lib/useInterval';
interface Props { type Props = {
id: string; id: string;
name?: string; name?: string;
profileName?: string; profileName?: string;
phoneNumber: string; phoneNumber: string;
memberCount: number; memberCount: number;
description: string; avatarPath: string | null;
avatarPath: string;
timerOptions: Array<TimerOption>; timerOptions: Array<TimerOption>;
isPublic: boolean; isPublic: boolean;
isAdmin: boolean; isAdmin: boolean;
isKickedFromGroup: boolean; isKickedFromGroup: boolean;
left: boolean; left: boolean;
isBlocked: boolean; isBlocked: boolean;
isShowing: boolean;
isGroup: boolean; isGroup: boolean;
memberAvatars?: Array<ConversationAvatar>; // this is added by usingClosedConversationDetails memberAvatars?: Array<ConversationAvatar>; // this is added by usingClosedConversationDetails
onGoBack: () => void; onGoBack: () => void;
onShowLightBox: (lightboxOptions?: LightBoxOptions) => void; onShowLightBox: (lightboxOptions?: LightBoxOptions) => void;
theme: DefaultTheme; };
}
async function getMediaGalleryProps(
interface State { conversationId: string,
documents: Array<any>; medias: Array<MediaItemType>,
media: Array<any>; onShowLightBox: (lightboxOptions?: LightBoxOptions) => void
): Promise<{
documents: Array<MediaItemType>;
media: Array<MediaItemType>;
onItemClick: any; onItemClick: any;
} }> {
// We fetch more documents than media as they dont require to be loaded
class SessionRightPanel extends React.Component<Props, State> { // into memory right away. Revisit this once we have infinite scrolling:
public constructor(props: Props) { const rawMedia = await getMessagesWithVisualMediaAttachments(conversationId, {
super(props); limit: Constants.CONVERSATION.DEFAULT_MEDIA_FETCH_COUNT,
});
const rawDocuments = await getMessagesWithFileAttachments(conversationId, {
limit: Constants.CONVERSATION.DEFAULT_DOCUMENTS_FETCH_COUNT,
});
const media = _.flatten(
rawMedia.map(attributes => {
const { attachments, source, id, timestamp, serverTimestamp, received_at } = attributes;
return (attachments || [])
.filter(
(attachment: AttachmentTypeWithPath) =>
attachment.thumbnail && !attachment.pending && !attachment.error
)
.map((attachment: AttachmentTypeWithPath, index: number) => {
const { thumbnail } = attachment;
const mediaItem: MediaItemType = {
objectURL: window.Signal.Migrations.getAbsoluteAttachmentPath(attachment.path),
thumbnailObjectUrl: thumbnail
? window.Signal.Migrations.getAbsoluteAttachmentPath(thumbnail.path)
: null,
contentType: attachment.contentType || '',
index,
messageTimestamp: timestamp || serverTimestamp || received_at || 0,
messageSender: source,
messageId: id,
attachment,
};
return mediaItem;
});
})
);
// Unlike visual media, only one non-image attachment is supported
const documents = rawDocuments.map(attributes => {
// this is to not fail if the attachment is invalid (could be a Long Attachment type which is not supported)
if (!attributes.attachments?.length) {
// window?.log?.info(
// 'Got a message with an empty list of attachment. Skipping...'
// );
return null;
}
const attachment = attributes.attachments[0];
const { source, id, timestamp, serverTimestamp, received_at } = attributes;
this.state = { return {
documents: Array<any>(), contentType: attachment.contentType,
media: Array<any>(), index: 0,
onItemClick: undefined, attachment,
messageTimestamp: timestamp || serverTimestamp || received_at || 0,
messageSender: source,
messageId: id,
}; };
} });
public componentWillMount() { const saveAttachment = async ({
void this.getMediaGalleryProps().then(({ documents, media, onItemClick }) => { attachment,
this.setState({ messageTimestamp,
documents, messageSender,
media, }: {
onItemClick, attachment: AttachmentTypeWithPath;
}); messageTimestamp: number;
messageSender: string;
}) => {
const timestamp = messageTimestamp;
attachment.url = await getDecryptedMediaUrl(attachment.url, attachment.contentType);
save({
attachment,
document,
getAbsolutePath: window.Signal.Migrations.getAbsoluteAttachmentPath,
timestamp,
}); });
} await sendDataExtractionNotification(conversationId, messageSender, timestamp);
};
public componentDidUpdate() { const onItemClick = (event: ItemClickEvent) => {
const mediaScanInterval = 1000; if (!event) {
console.warn('no event');
setTimeout(() => { return;
void this.getMediaGalleryProps().then(({ documents, media, onItemClick }) => {
const { documents: oldDocs, media: oldMedias } = this.state;
if (oldDocs.length !== documents.length || oldMedias.length !== media.length) {
this.setState({
documents,
media,
onItemClick,
});
}
});
}, mediaScanInterval);
}
public async getMediaGalleryProps(): Promise<{
documents: Array<any>;
media: Array<any>;
onItemClick: any;
}> {
// We fetch more documents than media as they dont require to be loaded
// into memory right away. Revisit this once we have infinite scrolling:
const conversationId = this.props.id;
const rawMedia = await getMessagesWithVisualMediaAttachments(conversationId, {
limit: Constants.CONVERSATION.DEFAULT_MEDIA_FETCH_COUNT,
});
const rawDocuments = await getMessagesWithFileAttachments(conversationId, {
limit: Constants.CONVERSATION.DEFAULT_DOCUMENTS_FETCH_COUNT,
});
// First we upgrade these messages to ensure that they have thumbnails
const max = rawMedia.length;
for (let i = 0; i < max; i += 1) {
const message = rawMedia[i];
const { schemaVersion } = message;
if (schemaVersion < message.VERSION_NEEDED_FOR_DISPLAY) {
// Yep, we really do want to wait for each of these
// eslint-disable-next-line no-await-in-loop
rawMedia[i] = await window.Signal.Migrations.upgradeMessageSchema(message);
// eslint-disable-next-line no-await-in-loop
await rawMedia[i].commit();
}
} }
const { mediaItem, type } = event;
const media = _.flatten( switch (type) {
rawMedia.map((message: { attachments: any }) => { case 'documents': {
const { attachments } = message; void saveAttachment({
messageSender: mediaItem.messageSender,
return (attachments || []) messageTimestamp: mediaItem.messageTimestamp,
.filter( attachment: mediaItem.attachment,
(attachment: { thumbnail: any; pending: any; error: any }) => });
attachment.thumbnail && !attachment.pending && !attachment.error break;
)
.map((attachment: { path?: any; contentType?: any; thumbnail?: any }, index: any) => {
const { thumbnail } = attachment;
return {
objectURL: window.Signal.Migrations.getAbsoluteAttachmentPath(attachment.path),
thumbnailObjectUrl: thumbnail
? window.Signal.Migrations.getAbsoluteAttachmentPath(thumbnail.path)
: null,
contentType: attachment.contentType,
index,
attachment,
message,
};
});
})
);
// Unlike visual media, only one non-image attachment is supported
const documents = rawDocuments.map((message: { attachments: Array<any> }) => {
// this is to not fail if the attachment is invalid (could be a Long Attachment type which is not supported)
if (!message.attachments?.length) {
// window?.log?.info(
// 'Got a message with an empty list of attachment. Skipping...'
// );
return null;
} }
const attachment = message.attachments[0];
return {
contentType: attachment.contentType,
index: 0,
attachment,
message,
};
});
const saveAttachment = async ({ attachment, message }: any = {}) => { case 'media': {
const timestamp = message.received_at as number | undefined; const lightBoxOptions: LightBoxOptions = {
attachment.url = await getDecryptedMediaUrl(attachment.url, attachment.contentType); media: medias,
save({ attachment: mediaItem.attachment,
attachment, };
document,
getAbsolutePath: window.Signal.Migrations.getAbsoluteAttachmentPath,
timestamp,
});
await sendDataExtractionNotification(this.props.id, message?.source, timestamp);
};
const onItemClick = ({ message, attachment, type }: any) => {
switch (type) {
case 'documents': {
void saveAttachment({ message, attachment });
break;
}
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,
messageTimestamp,
} as LightBoxOptions;
this.onShowLightBox(lightBoxOptions);
break;
}
default: onShowLightBox(lightBoxOptions);
throw new TypeError(`Unknown attachment type: '${type}'`); break;
} }
};
return {
media,
documents: _.compact(documents), // remove null
onItemClick,
};
}
public onShowLightBox(lightboxOptions: LightBoxOptions) {
this.props.onShowLightBox(lightboxOptions);
}
// tslint:disable-next-line: cyclomatic-complexity
public render() {
const {
id,
memberCount,
name,
timerOptions,
isKickedFromGroup,
left,
isPublic,
isAdmin,
isBlocked,
isGroup,
} = this.props;
const { documents, media, onItemClick } = this.state;
const showMemberCount = !!(memberCount && memberCount > 0);
const commonNoShow = isKickedFromGroup || left || isBlocked;
const hasDisappearingMessages = !isPublic && !commonNoShow;
const leaveGroupString = isPublic
? window.i18n('leaveGroup')
: isKickedFromGroup
? window.i18n('youGotKickedFromGroup')
: left
? window.i18n('youLeftTheGroup')
: window.i18n('leaveGroup');
const disappearingMessagesOptions = timerOptions.map(option => {
return {
content: option.name,
onClick: async () => {
await setDisappearingMessagesByConvoId(id, option.value);
},
};
});
const showUpdateGroupNameButton = isAdmin && !commonNoShow; default:
const showAddRemoveModeratorsButton = isAdmin && !commonNoShow && isPublic; throw new TypeError(`Unknown attachment type: '${type}'`);
}
};
const showUpdateGroupMembersButton = !isPublic && isGroup && !commonNoShow; return {
media,
documents: _.compact(documents), // remove null
onItemClick,
};
}
const deleteConvoAction = isPublic // tslint:disable: cyclomatic-complexity
? () => { // tslint:disable: max-func-body-length
deleteMessagesByConvoIdWithConfirmation(id); export const SessionRightPanelWithDetails = (props: Props) => {
const [documents, setDocuments] = useState<Array<MediaItemType>>([]);
const [media, setMedia] = useState<Array<MediaItemType>>([]);
const [onItemClick, setOnItemClick] = useState<any>(undefined);
const theme = useTheme();
useEffect(() => {
let isRunning = true;
if (props.isShowing) {
void getMediaGalleryProps(props.id, media, props.onShowLightBox).then(results => {
console.warn('results2', results);
if (isRunning) {
setDocuments(results.documents);
setMedia(results.media);
setOnItemClick(results.onItemClick);
} }
: () => { });
showLeaveGroupByConvoId(id); }
};
return (
<div className="group-settings">
{this.renderHeader()}
<h2>{name}</h2>
{showMemberCount && (
<>
<SpacerLG />
<div role="button" className="subtle">
{window.i18n('members', memberCount)}
</div>
<SpacerLG />
</>
)}
<input className="description" placeholder={window.i18n('description')} />
{showUpdateGroupNameButton && (
<div
className="group-settings-item"
role="button"
onClick={async () => {
await showUpdateGroupNameByConvoId(id);
}}
>
{isPublic ? window.i18n('editGroup') : window.i18n('editGroupName')}
</div>
)}
{showAddRemoveModeratorsButton && (
<>
<div
className="group-settings-item"
role="button"
onClick={() => {
showAddModeratorsByConvoId(id);
}}
>
{window.i18n('addModerators')}
</div>
<div
className="group-settings-item"
role="button"
onClick={() => {
showRemoveModeratorsByConvoId(id);
}}
>
{window.i18n('removeModerators')}
</div>
</>
)}
{showUpdateGroupMembersButton && ( return () => {
<div isRunning = false;
className="group-settings-item" return;
role="button" };
onClick={async () => { }, [props.isShowing, props.id]);
await showUpdateGroupMembersByConvoId(id);
}} useInterval(async () => {
> if (props.isShowing) {
{window.i18n('groupMembers')} const results = await getMediaGalleryProps(props.id, media, props.onShowLightBox);
</div> console.warn('results', results);
)} if (results.documents.length !== documents.length || results.media.length !== media.length) {
setDocuments(results.documents);
{hasDisappearingMessages && ( setMedia(results.media);
<SessionDropdown setOnItemClick(results.onItemClick);
label={window.i18n('disappearingMessages')} }
options={disappearingMessagesOptions} }
/> }, 10000);
)}
<MediaGallery documents={documents} media={media} onItemClick={onItemClick} />
{isGroup && (
// tslint:disable-next-line: use-simple-attributes
<SessionButton
text={leaveGroupString}
buttonColor={SessionButtonColor.Danger}
disabled={isKickedFromGroup || left}
buttonType={SessionButtonType.SquareOutline}
onClick={deleteConvoAction}
/>
)}
</div>
);
}
private renderHeader() { function renderHeader() {
const { const { memberAvatars, onGoBack, avatarPath, profileName, phoneNumber } = props;
memberAvatars,
id,
onGoBack,
avatarPath,
isAdmin,
isPublic,
isKickedFromGroup,
isBlocked,
name,
profileName,
phoneNumber,
left,
} = this.props;
const showInviteContacts = (isPublic || isAdmin) && !isKickedFromGroup && !isBlocked && !left; const showInviteContacts = (isPublic || isAdmin) && !isKickedFromGroup && !isBlocked && !left;
const userName = name || profileName || phoneNumber; const userName = name || profileName || phoneNumber;
@ -382,10 +240,10 @@ class SessionRightPanel extends React.Component<Props, State> {
iconSize={SessionIconSize.Medium} iconSize={SessionIconSize.Medium}
iconRotation={270} iconRotation={270}
onClick={onGoBack} onClick={onGoBack}
theme={this.props.theme} theme={theme}
/> />
<Avatar <Avatar
avatarPath={avatarPath} avatarPath={avatarPath || ''}
name={userName} name={userName}
size={AvatarSize.XL} size={AvatarSize.XL}
memberAvatars={memberAvatars} memberAvatars={memberAvatars}
@ -397,17 +255,139 @@ class SessionRightPanel extends React.Component<Props, State> {
iconType={SessionIconType.AddUser} iconType={SessionIconType.AddUser}
iconSize={SessionIconSize.Medium} iconSize={SessionIconSize.Medium}
onClick={() => { onClick={() => {
showInviteContactByConvoId(this.props.id); showInviteContactByConvoId(props.id);
}} }}
theme={this.props.theme} theme={theme}
/> />
)} )}
</div> </div>
</div> </div>
); );
} }
}
export const SessionRightPanelWithDetails = usingClosedConversationDetails( const {
withTheme(SessionRightPanel) id,
); memberCount,
name,
timerOptions,
isKickedFromGroup,
left,
isPublic,
isAdmin,
isBlocked,
isGroup,
} = props;
const showMemberCount = !!(memberCount && memberCount > 0);
const commonNoShow = isKickedFromGroup || left || isBlocked;
console.warn('AUDRIC: render right panel');
const hasDisappearingMessages = !isPublic && !commonNoShow;
const leaveGroupString = isPublic
? window.i18n('leaveGroup')
: isKickedFromGroup
? window.i18n('youGotKickedFromGroup')
: left
? window.i18n('youLeftTheGroup')
: window.i18n('leaveGroup');
const disappearingMessagesOptions = timerOptions.map(option => {
return {
content: option.name,
onClick: async () => {
await setDisappearingMessagesByConvoId(id, option.value);
},
};
});
const showUpdateGroupNameButton = isAdmin && !commonNoShow;
const showAddRemoveModeratorsButton = isAdmin && !commonNoShow && isPublic;
const showUpdateGroupMembersButton = !isPublic && isGroup && !commonNoShow;
const deleteConvoAction = isPublic
? () => {
deleteMessagesByConvoIdWithConfirmation(id);
}
: () => {
showLeaveGroupByConvoId(id);
};
console.warn('onItemClick', onItemClick);
return (
<div className="group-settings">
{renderHeader()}
<h2>{name}</h2>
{showMemberCount && (
<>
<SpacerLG />
<div role="button" className="subtle">
{window.i18n('members', memberCount)}
</div>
<SpacerLG />
</>
)}
{showUpdateGroupNameButton && (
<div
className="group-settings-item"
role="button"
onClick={async () => {
await showUpdateGroupNameByConvoId(id);
}}
>
{isPublic ? window.i18n('editGroup') : window.i18n('editGroupName')}
</div>
)}
{showAddRemoveModeratorsButton && (
<>
<div
className="group-settings-item"
role="button"
onClick={() => {
showAddModeratorsByConvoId(id);
}}
>
{window.i18n('addModerators')}
</div>
<div
className="group-settings-item"
role="button"
onClick={() => {
showRemoveModeratorsByConvoId(id);
}}
>
{window.i18n('removeModerators')}
</div>
</>
)}
{showUpdateGroupMembersButton && (
<div
className="group-settings-item"
role="button"
onClick={async () => {
await showUpdateGroupMembersByConvoId(id);
}}
>
{window.i18n('groupMembers')}
</div>
)}
{hasDisappearingMessages && (
<SessionDropdown
label={window.i18n('disappearingMessages')}
options={disappearingMessagesOptions}
/>
)}
<MediaGallery documents={documents} media={media} onItemClick={onItemClick} />
{isGroup && (
// tslint:disable-next-line: use-simple-attributes
<SessionButton
text={leaveGroupString}
buttonColor={SessionButtonColor.Danger}
disabled={isKickedFromGroup || left}
buttonType={SessionButtonType.SquareOutline}
onClick={deleteConvoAction}
/>
)}
</div>
);
};

@ -917,7 +917,7 @@ async function callChannel(name: string): Promise<any> {
export async function getMessagesWithVisualMediaAttachments( export async function getMessagesWithVisualMediaAttachments(
conversationId: string, conversationId: string,
options?: { limit: number } options?: { limit: number }
): Promise<any> { ): Promise<Array<MessageAttributes>> {
return channels.getMessagesWithVisualMediaAttachments(conversationId, { return channels.getMessagesWithVisualMediaAttachments(conversationId, {
limit: options?.limit, limit: options?.limit,
}); });
@ -926,7 +926,7 @@ export async function getMessagesWithVisualMediaAttachments(
export async function getMessagesWithFileAttachments( export async function getMessagesWithFileAttachments(
conversationId: string, conversationId: string,
options?: { limit: number } options?: { limit: number }
): Promise<any> { ): Promise<Array<MessageAttributes>> {
return channels.getMessagesWithFileAttachments(conversationId, { return channels.getMessagesWithFileAttachments(conversationId, {
limit: options?.limit, limit: options?.limit,
}); });

@ -892,7 +892,6 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
const unreadCount = await this.getUnreadCount(); const unreadCount = await this.getUnreadCount();
this.set({ unreadCount }); this.set({ unreadCount });
await this.commit(); await this.commit();
return model; return model;
} }
@ -971,12 +970,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
(m: any) => m.get('received_at') > newestUnreadDate (m: any) => m.get('received_at') > newestUnreadDate
); );
const ourNumber = UserUtils.getOurPubKeyStrFromCache(); const ourNumber = UserUtils.getOurPubKeyStrFromCache();
return !stillUnread.some( return !stillUnread.some(m => m.getPropsForMessage()?.text?.indexOf(`@${ourNumber}`) !== -1);
(m: any) =>
m.propsForMessage &&
m.propsForMessage.text &&
m.propsForMessage.text.indexOf(`@${ourNumber}`) !== -1
);
})(); })();
if (mentionRead) { if (mentionRead) {

@ -27,6 +27,7 @@ import {
LastMessageStatusType, LastMessageStatusType,
MessageModelProps, MessageModelProps,
MessagePropsDetails, MessagePropsDetails,
PropsForAttachment,
PropsForExpirationTimer, PropsForExpirationTimer,
PropsForGroupInvitation, PropsForGroupInvitation,
PropsForGroupUpdate, PropsForGroupUpdate,
@ -54,6 +55,7 @@ import { getV2OpenGroupRoom } from '../data/opengroups';
import { getMessageController } from '../session/messages'; import { getMessageController } from '../session/messages';
import { isUsFromCache } from '../session/utils/User'; import { isUsFromCache } from '../session/utils/User';
import { perfEnd, perfStart } from '../session/utils/Performance'; import { perfEnd, perfStart } from '../session/utils/Performance';
import { AttachmentType, AttachmentTypeWithPath } from '../types/Attachment';
export class MessageModel extends Backbone.Model<MessageAttributes> { export class MessageModel extends Backbone.Model<MessageAttributes> {
constructor(attributes: MessageAttributesOptionals) { constructor(attributes: MessageAttributesOptionals) {
@ -94,7 +96,6 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
propsForTimerNotification: this.getPropsForTimerNotification(), propsForTimerNotification: this.getPropsForTimerNotification(),
}; };
perfEnd(`getPropsMessage-${this.id}`, 'getPropsMessage'); perfEnd(`getPropsMessage-${this.id}`, 'getPropsMessage');
return messageProps; return messageProps;
} }
@ -623,7 +624,7 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
const previews = this.get('preview') || []; const previews = this.get('preview') || [];
return previews.map((preview: any) => { return previews.map((preview: any) => {
let image = null; let image: PropsForAttachment | null = null;
try { try {
if (preview.image) { if (preview.image) {
image = this.getPropsForAttachment(preview.image); image = this.getPropsForAttachment(preview.image);
@ -667,29 +668,39 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
}; };
} }
public getPropsForAttachment(attachment: { public getPropsForAttachment(attachment: AttachmentTypeWithPath): PropsForAttachment | null {
path?: string;
pending?: boolean;
flags: number;
size: number;
screenshot: any;
thumbnail: any;
}) {
if (!attachment) { if (!attachment) {
return null; return null;
} }
const { path, pending, flags, size, screenshot, thumbnail } = attachment; const {
id,
path,
contentType,
width,
height,
pending,
flags,
size,
screenshot,
thumbnail,
fileName,
} = attachment;
const isVoiceMessage =
// tslint:disable-next-line: no-bitwise
Boolean(flags && flags & SignalService.AttachmentPointer.Flags.VOICE_MESSAGE) || false;
return { return {
...attachment, id: id ? `${id}` : undefined,
contentType,
size: size || 0,
width: width || 0,
height: height || 0,
path,
fileName,
fileSize: size ? filesize(size) : null, fileSize: size ? filesize(size) : null,
isVoiceMessage: isVoiceMessage,
flags && pending: Boolean(pending),
// eslint-disable-next-line no-bitwise
// tslint:disable-next-line: no-bitwise
flags & SignalService.AttachmentPointer.Flags.VOICE_MESSAGE,
pending,
url: path ? window.Signal.Migrations.getAbsoluteAttachmentPath(path) : null, url: path ? window.Signal.Migrations.getAbsoluteAttachmentPath(path) : null,
screenshot: screenshot screenshot: screenshot
? { ? {
@ -1140,18 +1151,23 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
} }
public isTrustedForAttachmentDownload() { public isTrustedForAttachmentDownload() {
const senderConvoId = this.getSource(); try {
const isClosedGroup = this.getConversation()?.isClosedGroup() || false; const senderConvoId = this.getSource();
if (!!this.get('isPublic') || isClosedGroup || isUsFromCache(senderConvoId)) { const isClosedGroup = this.getConversation()?.isClosedGroup() || false;
return true; if (!!this.get('isPublic') || isClosedGroup || isUsFromCache(senderConvoId)) {
} return true;
// check the convo from this user }
// we want the convo of the sender of this message // check the convo from this user
const senderConvo = getConversationController().get(senderConvoId); // we want the convo of the sender of this message
if (!senderConvo) { const senderConvo = getConversationController().get(senderConvoId);
if (!senderConvo) {
return false;
}
return senderConvo.get('isTrustedForAttachmentDownload') || false;
} catch (e) {
window.log.warn('isTrustedForAttachmentDownload: error; ', e.message);
return false; return false;
} }
return senderConvo.get('isTrustedForAttachmentDownload') || false;
} }
} }
export class MessageCollection extends Backbone.Collection<MessageModel> {} export class MessageCollection extends Backbone.Collection<MessageModel> {}

@ -228,7 +228,6 @@ export interface MessageRegularProps {
authorProfileName?: string; authorProfileName?: string;
authorName?: string; authorName?: string;
messageId?: string; messageId?: string;
onClick: (data: any) => void;
referencedMessageNotFound: boolean; referencedMessageNotFound: boolean;
}; };
previews: Array<any>; previews: Array<any>;
@ -261,5 +260,5 @@ export interface MessageRegularProps {
playableMessageIndex?: number; playableMessageIndex?: number;
nextMessageToPlay?: number; nextMessageToPlay?: number;
playNextMessage?: (value: number) => any; playNextMessage?: (value: number) => void;
} }

@ -71,7 +71,7 @@ export function getUnpaddedAttachment(
export function addAttachmentPadding(data: ArrayBuffer): ArrayBuffer { export function addAttachmentPadding(data: ArrayBuffer): ArrayBuffer {
const originalUInt = new Uint8Array(data); const originalUInt = new Uint8Array(data);
window?.log?.info('Adding attchment padding...'); window?.log?.info('Adding attachment padding...');
const paddedSize = Math.max( const paddedSize = Math.max(
541, 541,

@ -46,16 +46,10 @@ export class DataExtractionNotificationMessage extends ContentMessage {
export const sendDataExtractionNotification = async ( export const sendDataExtractionNotification = async (
conversationId: string, conversationId: string,
attachmentSender: string, attachmentSender: string,
referencedAttachmentTimestamp?: number referencedAttachmentTimestamp: number
) => { ) => {
const convo = getConversationController().get(conversationId); const convo = getConversationController().get(conversationId);
if ( if (!convo || !convo.isPrivate() || convo.isMe() || UserUtils.isUsFromCache(attachmentSender)) {
!convo ||
!convo.isPrivate() ||
convo.isMe() ||
UserUtils.isUsFromCache(PubKey.cast(attachmentSender)) ||
!referencedAttachmentTimestamp
) {
window.log.warn('Not sending saving attachment notification for', attachmentSender); window.log.warn('Not sending saving attachment notification for', attachmentSender);
return; return;
} }

@ -669,7 +669,8 @@ const sendOnionRequestHandlingSnodeEject = async ({
}): Promise<SnodeResponse> => { }): Promise<SnodeResponse> => {
// this sendOnionRequest() call has to be the only one like this. // this sendOnionRequest() call has to be the only one like this.
// If you need to call it, call it through sendOnionRequestHandlingSnodeEject because this is the one handling path rebuilding and known errors // If you need to call it, call it through sendOnionRequestHandlingSnodeEject because this is the one handling path rebuilding and known errors
let response, decodingSymmetricKey; let response;
let decodingSymmetricKey;
try { try {
// this might throw a timeout error // this might throw a timeout error
const result = await sendOnionRequest({ const result = await sendOnionRequest({

@ -11,6 +11,7 @@ import {
MessageModelType, MessageModelType,
PropsForDataExtractionNotification, PropsForDataExtractionNotification,
} from '../../models/messageType'; } from '../../models/messageType';
import { AttachmentType } from '../../types/Attachment';
export type MessageModelProps = { export type MessageModelProps = {
propsForMessage: PropsForMessage; propsForMessage: PropsForMessage;
@ -101,6 +102,34 @@ export type PropsForSearchResults = {
snippet?: string; //not sure about the type of snippet snippet?: string; //not sure about the type of snippet
}; };
export type PropsForAttachment = {
id?: string;
contentType: string;
size: number;
width?: number;
height?: number;
url: string;
path?: string;
fileSize: string | null;
isVoiceMessage: boolean;
pending: boolean;
fileName: string;
screenshot: {
contentType: string;
width: number;
height: number;
url?: string;
path?: string;
} | null;
thumbnail: {
contentType: string;
width: number;
height: number;
url?: string;
path?: string;
} | null;
};
export type PropsForMessage = { export type PropsForMessage = {
text: string | null; text: string | null;
id: string; id: string;
@ -114,7 +143,7 @@ export type PropsForMessage = {
authorPhoneNumber: string; authorPhoneNumber: string;
conversationType: ConversationTypeEnum; conversationType: ConversationTypeEnum;
convoId: string; convoId: string;
attachments: any; attachments: Array<PropsForAttachment>;
previews: any; previews: any;
quote: any; quote: any;
authorAvatarPath: string | null; authorAvatarPath: string | null;

@ -15,20 +15,20 @@ const generatedMessageTimestamp = Date.now();
const toMediaItem = (date: Date): MediaItemType => ({ const toMediaItem = (date: Date): MediaItemType => ({
objectURL: date.toUTCString(), objectURL: date.toUTCString(),
index: 0, index: 0,
message: {
id: 'id',
received_at: date.getTime(),
sent_at: date.getTime(),
attachments: [],
},
attachment: { attachment: {
fileName: 'fileName', fileName: 'fileName',
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
url: 'url', url: 'url',
fileSize: null,
screenshot: null,
thumbnail: null,
path: '123456',
id: 123456,
}, },
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
messageSender: generatedMessageSenderKey, messageSender: generatedMessageSenderKey,
messageTimestamp: generatedMessageTimestamp, messageTimestamp: generatedMessageTimestamp,
messageId: '123456',
}); });
// tslint:disable: max-func-body-length // tslint:disable: max-func-body-length
@ -62,37 +62,37 @@ describe('groupMediaItemsByDate', () => {
index: 0, index: 0,
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
message: {
id: 'id',
sent_at: 1523534400000,
received_at: 1523534400000,
attachments: [],
},
attachment: { attachment: {
fileName: 'fileName', fileName: 'fileName',
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
url: 'url', url: 'url',
fileSize: null,
screenshot: null,
thumbnail: null,
path: '123456',
id: 123456,
}, },
messageSender: generatedMessageSenderKey, messageSender: generatedMessageSenderKey,
messageTimestamp: generatedMessageTimestamp, messageTimestamp: generatedMessageTimestamp,
messageId: '123456',
}, },
{ {
objectURL: 'Thu, 12 Apr 2018 00:01:00 GMT', objectURL: 'Thu, 12 Apr 2018 00:01:00 GMT',
index: 0, index: 0,
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
message: {
id: 'id',
received_at: 1523491260000,
sent_at: 1523491260000,
attachments: [],
},
attachment: { attachment: {
fileName: 'fileName', fileName: 'fileName',
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
url: 'url', url: 'url',
fileSize: null,
screenshot: null,
thumbnail: null,
path: '123456',
id: 123456,
}, },
messageSender: generatedMessageSenderKey, messageSender: generatedMessageSenderKey,
messageTimestamp: generatedMessageTimestamp, messageTimestamp: generatedMessageTimestamp,
messageId: '123456',
}, },
], ],
}, },
@ -103,20 +103,19 @@ describe('groupMediaItemsByDate', () => {
objectURL: 'Wed, 11 Apr 2018 23:59:00 GMT', objectURL: 'Wed, 11 Apr 2018 23:59:00 GMT',
index: 0, index: 0,
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
message: {
id: 'id',
received_at: 1523491140000,
sent_at: 1523491140000,
attachments: [],
},
attachment: { attachment: {
fileName: 'fileName', fileName: 'fileName',
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
url: 'url', url: 'url',
fileSize: null,
screenshot: null,
thumbnail: null,
path: '123456',
id: 123456,
}, },
messageSender: generatedMessageSenderKey, messageSender: generatedMessageSenderKey,
messageTimestamp: generatedMessageTimestamp, messageTimestamp: generatedMessageTimestamp,
messageId: '123456',
}, },
], ],
}, },
@ -127,20 +126,19 @@ describe('groupMediaItemsByDate', () => {
objectURL: 'Mon, 09 Apr 2018 00:01:00 GMT', objectURL: 'Mon, 09 Apr 2018 00:01:00 GMT',
index: 0, index: 0,
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
message: {
id: 'id',
received_at: 1523232060000,
sent_at: 1523232060000,
attachments: [],
},
attachment: { attachment: {
fileName: 'fileName', fileName: 'fileName',
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
url: 'url', url: 'url',
fileSize: null,
screenshot: null,
thumbnail: null,
path: '123456',
id: 123456,
}, },
messageSender: generatedMessageSenderKey, messageSender: generatedMessageSenderKey,
messageTimestamp: generatedMessageTimestamp, messageTimestamp: generatedMessageTimestamp,
messageId: '123456',
}, },
], ],
}, },
@ -151,39 +149,37 @@ describe('groupMediaItemsByDate', () => {
objectURL: 'Sun, 08 Apr 2018 23:59:00 GMT', objectURL: 'Sun, 08 Apr 2018 23:59:00 GMT',
index: 0, index: 0,
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
message: {
id: 'id',
received_at: 1523231940000,
sent_at: 1523231940000,
attachments: [],
},
attachment: { attachment: {
fileName: 'fileName', fileName: 'fileName',
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
url: 'url', url: 'url',
fileSize: null,
screenshot: null,
thumbnail: null,
path: '123456',
id: 123456,
}, },
messageSender: generatedMessageSenderKey, messageSender: generatedMessageSenderKey,
messageTimestamp: generatedMessageTimestamp, messageTimestamp: generatedMessageTimestamp,
messageId: '123456',
}, },
{ {
objectURL: 'Sun, 01 Apr 2018 00:01:00 GMT', objectURL: 'Sun, 01 Apr 2018 00:01:00 GMT',
index: 0, index: 0,
message: {
id: 'id',
received_at: 1522540860000,
sent_at: 1522540860000,
attachments: [],
},
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
attachment: { attachment: {
fileName: 'fileName', fileName: 'fileName',
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
url: 'url', url: 'url',
fileSize: null,
screenshot: null,
thumbnail: null,
path: '123456',
id: 123456,
}, },
messageSender: generatedMessageSenderKey, messageSender: generatedMessageSenderKey,
messageTimestamp: generatedMessageTimestamp, messageTimestamp: generatedMessageTimestamp,
messageId: '123456',
}, },
], ],
}, },
@ -196,39 +192,37 @@ describe('groupMediaItemsByDate', () => {
objectURL: 'Sat, 31 Mar 2018 23:59:00 GMT', objectURL: 'Sat, 31 Mar 2018 23:59:00 GMT',
index: 0, index: 0,
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
message: {
id: 'id',
received_at: 1522540740000,
sent_at: 1522540740000,
attachments: [],
},
attachment: { attachment: {
fileName: 'fileName', fileName: 'fileName',
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
url: 'url', url: 'url',
fileSize: null,
screenshot: null,
thumbnail: null,
path: '123456',
id: 123456,
}, },
messageSender: generatedMessageSenderKey, messageSender: generatedMessageSenderKey,
messageTimestamp: generatedMessageTimestamp, messageTimestamp: generatedMessageTimestamp,
messageId: '123456',
}, },
{ {
objectURL: 'Thu, 01 Mar 2018 14:00:00 GMT', objectURL: 'Thu, 01 Mar 2018 14:00:00 GMT',
index: 0, index: 0,
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
message: {
id: 'id',
received_at: 1519912800000,
sent_at: 1519912800000,
attachments: [],
},
attachment: { attachment: {
fileName: 'fileName', fileName: 'fileName',
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
url: 'url', url: 'url',
fileSize: null,
screenshot: null,
thumbnail: null,
path: '123456',
id: 123456,
}, },
messageSender: generatedMessageSenderKey, messageSender: generatedMessageSenderKey,
messageTimestamp: generatedMessageTimestamp, messageTimestamp: generatedMessageTimestamp,
messageId: '123456',
}, },
], ],
}, },
@ -240,40 +234,38 @@ describe('groupMediaItemsByDate', () => {
{ {
objectURL: 'Mon, 28 Feb 2011 23:59:00 GMT', objectURL: 'Mon, 28 Feb 2011 23:59:00 GMT',
index: 0, index: 0,
message: {
id: 'id',
received_at: 1298937540000,
sent_at: 1298937540000,
attachments: [],
},
attachment: { attachment: {
fileName: 'fileName', fileName: 'fileName',
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
url: 'url', url: 'url',
fileSize: null,
screenshot: null,
thumbnail: null,
path: '123456',
id: 123456,
}, },
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
messageSender: generatedMessageSenderKey, messageSender: generatedMessageSenderKey,
messageTimestamp: generatedMessageTimestamp, messageTimestamp: generatedMessageTimestamp,
messageId: '123456',
}, },
{ {
objectURL: 'Tue, 01 Feb 2011 10:00:00 GMT', objectURL: 'Tue, 01 Feb 2011 10:00:00 GMT',
index: 0, index: 0,
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
message: {
id: 'id',
received_at: 1296554400000,
sent_at: 1296554400000,
attachments: [],
},
messageSender: generatedMessageSenderKey, messageSender: generatedMessageSenderKey,
messageTimestamp: generatedMessageTimestamp, messageTimestamp: generatedMessageTimestamp,
attachment: { attachment: {
fileName: 'fileName', fileName: 'fileName',
contentType: IMAGE_JPEG, contentType: IMAGE_JPEG,
url: 'url', url: 'url',
fileSize: null,
screenshot: null,
thumbnail: null,
path: '123456',
id: 123456,
}, },
messageId: '123456',
}, },
], ],
}, },

@ -15,6 +15,9 @@ describe('Attachment', () => {
fileName: 'funny-cat.mov', fileName: 'funny-cat.mov',
url: 'funny-cat.mov', url: 'funny-cat.mov',
contentType: MIME.IMAGE_GIF, contentType: MIME.IMAGE_GIF,
fileSize: null,
screenshot: null,
thumbnail: null,
}; };
assert.strictEqual(Attachment.getFileExtension(input), 'gif'); assert.strictEqual(Attachment.getFileExtension(input), 'gif');
}); });
@ -24,6 +27,9 @@ describe('Attachment', () => {
fileName: 'funny-cat.mov', fileName: 'funny-cat.mov',
url: 'funny-cat.mov', url: 'funny-cat.mov',
contentType: MIME.VIDEO_QUICKTIME, contentType: MIME.VIDEO_QUICKTIME,
fileSize: null,
screenshot: null,
thumbnail: null,
}; };
assert.strictEqual(Attachment.getFileExtension(input), 'mov'); assert.strictEqual(Attachment.getFileExtension(input), 'mov');
}); });
@ -33,6 +39,9 @@ describe('Attachment', () => {
fileName: 'funny-cat.odt', fileName: 'funny-cat.odt',
url: 'funny-cat.odt', url: 'funny-cat.odt',
contentType: MIME.ODT, contentType: MIME.ODT,
fileSize: null,
screenshot: null,
thumbnail: null,
}; };
assert.strictEqual(Attachment.getFileExtension(input), 'odt'); assert.strictEqual(Attachment.getFileExtension(input), 'odt');
}); });
@ -45,6 +54,9 @@ describe('Attachment', () => {
fileName: 'funny-cat.mov', fileName: 'funny-cat.mov',
url: 'funny-cat.mov', url: 'funny-cat.mov',
contentType: MIME.VIDEO_QUICKTIME, contentType: MIME.VIDEO_QUICKTIME,
fileSize: null,
screenshot: null,
thumbnail: null,
}; };
const actual = Attachment.getSuggestedFilename({ attachment }); const actual = Attachment.getSuggestedFilename({ attachment });
const expected = 'funny-cat.mov'; const expected = 'funny-cat.mov';
@ -55,6 +67,9 @@ describe('Attachment', () => {
fileName: 'funny-cat.mov', fileName: 'funny-cat.mov',
url: 'funny-cat.mov', url: 'funny-cat.mov',
contentType: MIME.VIDEO_QUICKTIME, contentType: MIME.VIDEO_QUICKTIME,
fileSize: null,
screenshot: null,
thumbnail: null,
}; };
const actual = Attachment.getSuggestedFilename({ const actual = Attachment.getSuggestedFilename({
attachment, attachment,
@ -68,6 +83,9 @@ describe('Attachment', () => {
fileName: 'funny-cat.ini', fileName: 'funny-cat.ini',
url: 'funny-cat.ini', url: 'funny-cat.ini',
contentType: '', contentType: '',
fileSize: null,
screenshot: null,
thumbnail: null,
}; };
const actual = Attachment.getSuggestedFilename({ const actual = Attachment.getSuggestedFilename({
attachment, attachment,
@ -82,6 +100,9 @@ describe('Attachment', () => {
fileName: 'funny-cat.txt', fileName: 'funny-cat.txt',
url: 'funny-cat.txt', url: 'funny-cat.txt',
contentType: 'text/plain', contentType: 'text/plain',
fileSize: null,
screenshot: null,
thumbnail: null,
}; };
const actual = Attachment.getSuggestedFilename({ const actual = Attachment.getSuggestedFilename({
attachment, attachment,
@ -95,6 +116,9 @@ describe('Attachment', () => {
fileName: 'funny-cat.json', fileName: 'funny-cat.json',
url: 'funny-cat.json', url: 'funny-cat.json',
contentType: '', contentType: '',
fileSize: null,
screenshot: null,
thumbnail: null,
}; };
const actual = Attachment.getSuggestedFilename({ const actual = Attachment.getSuggestedFilename({
attachment, attachment,
@ -110,6 +134,9 @@ describe('Attachment', () => {
contentType: MIME.VIDEO_QUICKTIME, contentType: MIME.VIDEO_QUICKTIME,
url: 'funny-cat.mov', url: 'funny-cat.mov',
fileName: 'funny-cat.mov', fileName: 'funny-cat.mov',
fileSize: null,
screenshot: null,
thumbnail: null,
}; };
const timestamp = moment('2000-01-01').toDate(); const timestamp = moment('2000-01-01').toDate();
const actual = Attachment.getSuggestedFilename({ const actual = Attachment.getSuggestedFilename({
@ -126,6 +153,9 @@ describe('Attachment', () => {
fileName: '', fileName: '',
url: 'funny-cat.mov', url: 'funny-cat.mov',
contentType: MIME.VIDEO_QUICKTIME, contentType: MIME.VIDEO_QUICKTIME,
fileSize: null,
screenshot: null,
thumbnail: null,
}; };
const timestamp = new Date(new Date(0).getTimezoneOffset() * 60 * 1000); const timestamp = new Date(new Date(0).getTimezoneOffset() * 60 * 1000);
const actual = Attachment.getSuggestedFilename({ const actual = Attachment.getSuggestedFilename({
@ -142,6 +172,9 @@ describe('Attachment', () => {
fileName: 'funny-cat.mov', fileName: 'funny-cat.mov',
url: 'funny-cat.mov', url: 'funny-cat.mov',
contentType: MIME.VIDEO_QUICKTIME, contentType: MIME.VIDEO_QUICKTIME,
fileSize: null,
screenshot: null,
thumbnail: null,
}; };
const timestamp = new Date(new Date(0).getTimezoneOffset() * 60 * 1000); const timestamp = new Date(new Date(0).getTimezoneOffset() * 60 * 1000);
const actual = Attachment.getSuggestedFilename({ const actual = Attachment.getSuggestedFilename({

@ -26,22 +26,44 @@ export interface AttachmentType {
url: string; url: string;
videoUrl?: string; videoUrl?: string;
size?: number; size?: number;
fileSize?: string; fileSize: string | null;
pending?: boolean; pending?: boolean;
width?: number; width?: number;
height?: number; height?: number;
screenshot?: { screenshot: {
height: number; height: number;
width: number; width: number;
url: string; url: string;
contentType: MIME.MIMEType; contentType: MIME.MIMEType;
}; } | null;
thumbnail?: { thumbnail: {
height: number; height: number;
width: number; width: number;
url: string; url: string;
contentType: MIME.MIMEType; contentType: MIME.MIMEType;
}; } | null;
}
export interface AttachmentTypeWithPath extends AttachmentType {
path: string;
id: number;
flags?: number;
error?: any;
screenshot: {
height: number;
width: number;
url: string;
contentType: MIME.MIMEType;
path?: string;
} | null;
thumbnail: {
height: number;
width: number;
url: string;
contentType: MIME.MIMEType;
path?: string;
} | null;
} }
// UI-focused functions // UI-focused functions

Loading…
Cancel
Save