From c8aa73626ef5a2be4f47755ff69f9612458ac4b6 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Tue, 6 Jul 2021 14:01:02 +1000 Subject: [PATCH] do not load right panel data unless it is visibl --- preload.js | 2 +- ts/components/CaptionEditor.tsx | 4 +- ts/components/LightboxGallery.tsx | 25 +- ts/components/OnionStatusPathDialog.tsx | 8 +- .../media-gallery/AttachmentSection.tsx | 11 +- .../media-gallery/DocumentListItem.tsx | 50 +- .../media-gallery/groupMediaItemsByDate.ts | 8 +- .../media-gallery/types/ItemClickEvent.ts | 6 +- .../conversation/SessionCompositionBox.tsx | 3 + .../conversation/SessionConversation.tsx | 65 +- .../conversation/SessionMessagesList.tsx | 11 +- .../conversation/SessionRightPanel.tsx | 616 +++++++++--------- ts/data/data.ts | 4 +- ts/models/conversation.ts | 8 +- ts/models/message.ts | 74 ++- ts/models/messageType.ts | 3 +- ts/session/crypto/BufferPadding.ts | 2 +- .../DataExtractionNotificationMessage.ts | 10 +- ts/session/snode_api/onions.ts | 3 +- ts/state/ducks/conversations.ts | 31 +- .../media-gallery/groupMessagesByDate_test.ts | 140 ++-- ts/test/types/Attachment_test.ts | 33 + ts/types/Attachment.ts | 32 +- 23 files changed, 604 insertions(+), 545 deletions(-) diff --git a/preload.js b/preload.js index e6875aa8b..00c0cfdf9 100644 --- a/preload.js +++ b/preload.js @@ -35,9 +35,9 @@ window.semver = semver; window.platform = process.platform; window.getTitle = () => title; window.getEnvironment = () => config.environment; -window.isDev = () => config.environment === 'development'; window.getAppInstance = () => config.appInstance; window.getVersion = () => config.version; +window.isDev = () => config.environment === 'development'; window.getExpiration = () => config.buildExpiration; window.getCommitHash = () => config.commitHash; window.getNodeVersion = () => config.node_version; diff --git a/ts/components/CaptionEditor.tsx b/ts/components/CaptionEditor.tsx index 432ac0364..296e97fdb 100644 --- a/ts/components/CaptionEditor.tsx +++ b/ts/components/CaptionEditor.tsx @@ -8,6 +8,7 @@ import { AttachmentType } from '../types/Attachment'; import { SessionInput } from './session/SessionInput'; import { SessionButton, SessionButtonColor, SessionButtonType } from './session/SessionButton'; import { darkTheme, lightTheme } from '../state/ducks/SessionTheme'; +import autoBind from 'auto-bind'; interface Props { attachment: AttachmentType; @@ -31,8 +32,7 @@ export class CaptionEditor extends React.Component { this.state = { caption: caption || '', }; - this.onSave = this.onSave.bind(this); - this.onChange = this.onChange.bind(this); + autoBind(this); this.inputRef = React.createRef(); } diff --git a/ts/components/LightboxGallery.tsx b/ts/components/LightboxGallery.tsx index 8519cb1b4..8f08361c3 100644 --- a/ts/components/LightboxGallery.tsx +++ b/ts/components/LightboxGallery.tsx @@ -5,9 +5,8 @@ import React, { useEffect, useState } from 'react'; import * as MIME from '../types/MIME'; 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 import useKey from 'react-use/lib/useKey'; @@ -16,22 +15,16 @@ export interface MediaItemType { thumbnailObjectUrl?: string; contentType: MIME.MIMEType; index: number; - attachment: AttachmentType; - message: Message; + attachment: AttachmentTypeWithPath; messageTimestamp: number; messageSender: string; + messageId: string; } type Props = { close: () => void; media: Array; - onSave?: (options: { - attachment: AttachmentType; - message: Message; - index: number; - messageTimestamp?: number; - messageSender: string; - }) => void; + onSave?: (saveData: MediaItemType) => void; selectedIndex: number; }; @@ -66,16 +59,10 @@ 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, - }); + onSave(mediaItem); }; - const objectURL = selectedMedia.objectURL || 'images/alert-outline.svg'; + const objectURL = selectedMedia?.objectURL || 'images/alert-outline.svg'; const { attachment } = selectedMedia; const saveCallback = onSave ? handleSave : undefined; diff --git a/ts/components/OnionStatusPathDialog.tsx b/ts/components/OnionStatusPathDialog.tsx index a7099318e..5c32cdea6 100644 --- a/ts/components/OnionStatusPathDialog.tsx +++ b/ts/components/OnionStatusPathDialog.tsx @@ -66,7 +66,7 @@ const OnionPathModalInner = () => { ); })} @@ -80,7 +80,11 @@ const OnionPathModalInner = () => { if (!labelText) { labelText = window.i18n('unknownCountry'); } - return labelText ?
{labelText}
: null; + return labelText ? ( +
+ {labelText} +
+ ) : null; })} diff --git a/ts/components/conversation/media-gallery/AttachmentSection.tsx b/ts/components/conversation/media-gallery/AttachmentSection.tsx index da2a63896..e4937e790 100644 --- a/ts/components/conversation/media-gallery/AttachmentSection.tsx +++ b/ts/components/conversation/media-gallery/AttachmentSection.tsx @@ -30,23 +30,23 @@ export class AttachmentSection extends React.Component { return mediaItems.map((mediaItem, position, array) => { const shouldShowSeparator = position < array.length - 1; - const { message, index, attachment } = mediaItem; + const { index, attachment, messageTimestamp, messageId } = mediaItem; const onClick = this.createClickHandler(mediaItem); switch (type) { case 'media': return ( - + ); case 'documents': return ( ); default: @@ -57,12 +57,11 @@ export class AttachmentSection extends React.Component { private readonly createClickHandler = (mediaItem: MediaItemType) => () => { const { onItemClick, type } = this.props; - const { message, attachment } = mediaItem; if (!onItemClick) { return; } - onItemClick({ type, message, attachment }); + onItemClick({ mediaItem, type }); }; } diff --git a/ts/components/conversation/media-gallery/DocumentListItem.tsx b/ts/components/conversation/media-gallery/DocumentListItem.tsx index 1ec504f75..1a765f29c 100644 --- a/ts/components/conversation/media-gallery/DocumentListItem.tsx +++ b/ts/components/conversation/media-gallery/DocumentListItem.tsx @@ -5,46 +5,30 @@ import moment from 'moment'; // tslint:disable-next-line:match-default-export-name import formatFileSize from 'filesize'; -interface Props { +type Props = { // Required timestamp: number; // Optional fileName?: string; - fileSize?: number; + fileSize?: number | null; onClick?: () => void; shouldShowSeparator?: boolean; -} +}; -export class DocumentListItem extends React.Component { - public static defaultProps: Partial = { - shouldShowSeparator: true, - }; +export const DocumentListItem = (props: Props) => { + const { shouldShowSeparator, fileName, fileSize, timestamp } = props; - public render() { - const { shouldShowSeparator } = this.props; + const defaultShowSeparator = shouldShowSeparator === undefined ? true : shouldShowSeparator; - return ( -
- {this.renderContent()} -
- ); - } - - private renderContent() { - const { fileName, fileSize, timestamp } = this.props; - - return ( -
+ return ( +
+
{fileName} @@ -56,6 +40,6 @@ export class DocumentListItem extends React.Component { {moment(timestamp).format('ddd, MMM D, Y')}
- ); - } -} +
+ ); +}; diff --git a/ts/components/conversation/media-gallery/groupMediaItemsByDate.ts b/ts/components/conversation/media-gallery/groupMediaItemsByDate.ts index 0e78649ec..ae7c91e6d 100644 --- a/ts/components/conversation/media-gallery/groupMediaItemsByDate.ts +++ b/ts/components/conversation/media-gallery/groupMediaItemsByDate.ts @@ -25,9 +25,9 @@ export const groupMediaItemsByDate = ( const referenceDateTime = moment.utc(timestamp); const sortedMediaItem = sortBy(mediaItems, mediaItem => { - const { message } = mediaItem; + const { messageTimestamp } = mediaItem; - return -message.received_at; + return -messageTimestamp; }); const messagesWithSection = sortedMediaItem.map(withSection(referenceDateTime)); const groupedMediaItem = groupBy(messagesWithSection, 'type'); @@ -102,8 +102,8 @@ const withSection = (referenceDateTime: moment.Moment) => ( const thisWeek = moment(referenceDateTime).startOf('isoWeek'); const thisMonth = moment(referenceDateTime).startOf('month'); - const { message } = mediaItem; - const mediaItemReceivedDate = moment.utc(message.received_at); + const { messageTimestamp } = mediaItem; + const mediaItemReceivedDate = moment.utc(messageTimestamp); if (mediaItemReceivedDate.isAfter(today)) { return { order: 0, diff --git a/ts/components/conversation/media-gallery/types/ItemClickEvent.ts b/ts/components/conversation/media-gallery/types/ItemClickEvent.ts index cfb3f21db..841d4ae3d 100644 --- a/ts/components/conversation/media-gallery/types/ItemClickEvent.ts +++ b/ts/components/conversation/media-gallery/types/ItemClickEvent.ts @@ -1,8 +1,6 @@ -import { AttachmentType } from '../../../../types/Attachment'; -import { Message } from './Message'; +import { MediaItemType } from '../../../LightboxGallery'; export interface ItemClickEvent { - message: Message; - attachment: AttachmentType; + mediaItem: MediaItemType; type: 'media' | 'documents'; } diff --git a/ts/components/session/conversation/SessionCompositionBox.tsx b/ts/components/session/conversation/SessionCompositionBox.tsx index 76d2c68f7..aa9ec4598 100644 --- a/ts/components/session/conversation/SessionCompositionBox.tsx +++ b/ts/components/session/conversation/SessionCompositionBox.tsx @@ -592,6 +592,9 @@ export class SessionCompositionBox extends React.Component { ...ret.image, url: URL.createObjectURL(blob), fileName: 'preview', + fileSize: null, + screenshot: null, + thumbnail: null, }; image = imageAttachment; } diff --git a/ts/components/session/conversation/SessionConversation.tsx b/ts/components/session/conversation/SessionConversation.tsx index c41a0bf51..4b1aaea38 100644 --- a/ts/components/session/conversation/SessionConversation.tsx +++ b/ts/components/session/conversation/SessionConversation.tsx @@ -17,11 +17,11 @@ import { SessionMessagesList } from './SessionMessagesList'; import { LightboxGallery, MediaItemType } from '../../LightboxGallery'; 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 * as MIME from '../../../types/MIME'; import { SessionFileDropzone } from './SessionFileDropzone'; -import { ConversationType } from '../../../state/ducks/conversations'; +import { ConversationType, PropsForMessage } from '../../../state/ducks/conversations'; import { MessageView } from '../../MainViewController'; import { pushUnblockToSend } from '../../../session/utils/Toast'; import { MessageDetail } from '../../conversation/MessageDetail'; @@ -39,6 +39,7 @@ import { updateMentionsMembers } from '../../../state/ducks/mentionsInput'; import { sendDataExtractionNotification } from '../../../session/messages/outgoing/controlMessage/DataExtractionNotificationMessage'; import { SessionButtonColor } from '../SessionButton'; +import { usingClosedConversationDetails } from '../usingClosedConversationDetails'; interface State { // Message sending progress messageProgressVisible: boolean; @@ -74,7 +75,7 @@ interface State { export interface LightBoxOptions { media: Array; - attachment: any; + attachment: AttachmentTypeWithPath; } interface Props { @@ -249,7 +250,6 @@ export class SessionConversation extends React.Component { return (
{this.renderHeader()}
- {/* { sendStatus={this.state.sendingProgressStatus} resetProgress={this.resetSendingProgress} /> */} -
{ theme={this.props.theme} />
-
- +
+ )
); } @@ -457,7 +459,7 @@ export class SessionConversation extends React.Component { phoneNumber: conversation.getNumber(), profileName: conversation.getProfileName(), avatarPath: conversation.getAvatarPath(), - isKickedFromGroup: conversation.get('isKickedFromGroup'), + isKickedFromGroup: Boolean(conversation.get('isKickedFromGroup')), left: conversation.get('left'), isGroup: !conversation.isPrivate(), isPublic: conversation.isPublic(), @@ -714,20 +716,25 @@ export class SessionConversation extends React.Component { }); } - private onClickAttachment(attachment: any, message: any) { - // message is MessageModelProps.propsForMessage I think - const media = (message.attachments || []).map((attachmentForMedia: any) => { + private onClickAttachment(attachment: AttachmentTypeWithPath, propsForMessage: PropsForMessage) { + let index = -1; + const media = (propsForMessage.attachments || []).map(attachmentForMedia => { + index++; + const messageTimestamp = + propsForMessage.timestamp || propsForMessage.serverTimestamp || propsForMessage.receivedAt; + return { - objectURL: attachmentForMedia.url, + index: _.clone(index), + objectURL: attachmentForMedia.url || undefined, 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. + messageSender: propsForMessage.authorPhoneNumber, + messageTimestamp, + messageId: propsForMessage.id, }; }); const lightBoxOptions: LightBoxOptions = { - media, + media: media as any, attachment, }; this.setState({ lightBoxOptions }); @@ -820,8 +827,9 @@ export class SessionConversation extends React.Component { private renderLightBox({ media, attachment }: LightBoxOptions) { const selectedIndex = media.length > 1 - ? media.findIndex((mediaMessage: any) => mediaMessage.attachment.path === attachment.path) + ? media.findIndex(mediaMessage => mediaMessage.attachment.path === attachment.path) : 0; + console.warn('renderLightBox', { media, attachment }); return ( { // THIS DOES NOT DOWNLOAD ANYTHING! it just saves it where the user wants private async saveAttachment({ attachment, - message, - index, messageTimestamp, messageSender, }: { attachment: AttachmentType; - message?: Message; - index?: number; - messageTimestamp?: number; + messageTimestamp: number; messageSender: string; }) { const { getAbsoluteAttachmentPath } = window.Signal.Migrations; @@ -854,7 +858,7 @@ export class SessionConversation extends React.Component { attachment, document, getAbsolutePath: getAbsoluteAttachmentPath, - timestamp: messageTimestamp || message?.received_at, + timestamp: messageTimestamp, }); await sendDataExtractionNotification( @@ -937,6 +941,9 @@ export class SessionConversation extends React.Component { videoUrl: objectUrl, url, isVoiceMessage: false, + fileSize: null, + screenshot: null, + thumbnail: null, }, ]); } catch (error) { @@ -958,6 +965,9 @@ export class SessionConversation extends React.Component { contentType, url: urlImage, isVoiceMessage: false, + fileSize: null, + screenshot: null, + thumbnail: null, }, ]); return; @@ -973,6 +983,9 @@ export class SessionConversation extends React.Component { contentType, url, isVoiceMessage: false, + fileSize: null, + screenshot: null, + thumbnail: null, }, ]); }; @@ -1017,6 +1030,9 @@ export class SessionConversation extends React.Component { fileName, url: '', isVoiceMessage: false, + fileSize: null, + screenshot: null, + thumbnail: null, }, ]); } @@ -1033,6 +1049,9 @@ export class SessionConversation extends React.Component { fileName, isVoiceMessage: false, url: '', + fileSize: null, + screenshot: null, + thumbnail: null, }, ]); } diff --git a/ts/components/session/conversation/SessionMessagesList.tsx b/ts/components/session/conversation/SessionMessagesList.tsx index d8c747412..bc035f61f 100644 --- a/ts/components/session/conversation/SessionMessagesList.tsx +++ b/ts/components/session/conversation/SessionMessagesList.tsx @@ -56,7 +56,7 @@ interface Props { messageTimestamp, }: { attachment: any; - messageTimestamp?: number; + messageTimestamp: number; messageSender: string; }) => void; onDeleteSelectedMessages: () => Promise; @@ -336,12 +336,17 @@ export class SessionMessagesList extends React.Component { }; regularProps.onClickAttachment = (attachment: AttachmentType) => { - this.props.onClickAttachment(attachment, messageProps); + this.props.onClickAttachment(attachment, messageProps.propsForMessage); }; regularProps.onDownload = (attachment: AttachmentType) => { + const messageTimestamp = + messageProps.propsForMessage.timestamp || + messageProps.propsForMessage.serverTimestamp || + messageProps.propsForMessage.receivedAt || + 0; this.props.onDownloadAttachment({ attachment, - messageTimestamp: messageProps.propsForMessage.timestamp, + messageTimestamp, messageSender: messageProps.propsForMessage.authorPhoneNumber, }); }; diff --git a/ts/components/session/conversation/SessionRightPanel.tsx b/ts/components/session/conversation/SessionRightPanel.tsx index 2f510191f..8b346ea11 100644 --- a/ts/components/session/conversation/SessionRightPanel.tsx +++ b/ts/components/session/conversation/SessionRightPanel.tsx @@ -1,18 +1,18 @@ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import { SessionIconButton, SessionIconSize, SessionIconType } from '../icon'; import { Avatar, AvatarSize } from '../../Avatar'; import { SessionButton, SessionButtonColor, SessionButtonType } from '../SessionButton'; import { SessionDropdown } from '../SessionDropdown'; import { MediaGallery } from '../../conversation/media-gallery/MediaGallery'; -import _ from 'lodash'; +import _, { noop } from 'lodash'; import { TimerOption } from '../../conversation/ConversationHeader'; import { Constants } from '../../../session'; import { ConversationAvatar, usingClosedConversationDetails, } from '../usingClosedConversationDetails'; -import { save } from '../../../types/Attachment'; -import { DefaultTheme, withTheme } from 'styled-components'; +import { AttachmentTypeWithPath, save } from '../../../types/Attachment'; +import { DefaultTheme, useTheme, withTheme } from 'styled-components'; import { getMessagesWithFileAttachments, getMessagesWithVisualMediaAttachments, @@ -32,345 +32,203 @@ import { showUpdateGroupMembersByConvoId, showUpdateGroupNameByConvoId, } 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; name?: string; profileName?: string; phoneNumber: string; memberCount: number; - description: string; - avatarPath: string; + avatarPath: string | null; timerOptions: Array; isPublic: boolean; isAdmin: boolean; isKickedFromGroup: boolean; left: boolean; isBlocked: boolean; + isShowing: boolean; isGroup: boolean; memberAvatars?: Array; // this is added by usingClosedConversationDetails onGoBack: () => void; onShowLightBox: (lightboxOptions?: LightBoxOptions) => void; - theme: DefaultTheme; -} - -interface State { - documents: Array; - media: Array; +}; + +async function getMediaGalleryProps( + conversationId: string, + medias: Array, + onShowLightBox: (lightboxOptions?: LightBoxOptions) => void +): Promise<{ + documents: Array; + media: Array; onItemClick: any; -} - -class SessionRightPanel extends React.Component { - public constructor(props: Props) { - super(props); +}> { + // We fetch more documents than media as they don’t require to be loaded + // into memory right away. Revisit this once we have infinite scrolling: + const rawMedia = await getMessagesWithVisualMediaAttachments(conversationId, { + 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 = { - documents: Array(), - media: Array(), - onItemClick: undefined, + return { + contentType: attachment.contentType, + index: 0, + attachment, + messageTimestamp: timestamp || serverTimestamp || received_at || 0, + messageSender: source, + messageId: id, }; - } - - public componentWillMount() { - void this.getMediaGalleryProps().then(({ documents, media, onItemClick }) => { - this.setState({ - documents, - media, - onItemClick, - }); + }); + + const saveAttachment = async ({ + attachment, + messageTimestamp, + messageSender, + }: { + 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 mediaScanInterval = 1000; - - setTimeout(() => { - 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; - media: Array; - onItemClick: any; - }> { - // We fetch more documents than media as they don’t 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 onItemClick = (event: ItemClickEvent) => { + if (!event) { + console.warn('no event'); + return; } - - const media = _.flatten( - rawMedia.map((message: { attachments: any }) => { - const { attachments } = message; - - return (attachments || []) - .filter( - (attachment: { thumbnail: any; pending: any; error: any }) => - attachment.thumbnail && !attachment.pending && !attachment.error - ) - .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 }) => { - // 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 { mediaItem, type } = event; + switch (type) { + case 'documents': { + void saveAttachment({ + messageSender: mediaItem.messageSender, + messageTimestamp: mediaItem.messageTimestamp, + attachment: mediaItem.attachment, + }); + break; } - const attachment = message.attachments[0]; - - return { - contentType: attachment.contentType, - index: 0, - attachment, - message, - }; - }); - const saveAttachment = async ({ attachment, message }: any = {}) => { - const timestamp = message.received_at as number | undefined; - attachment.url = await getDecryptedMediaUrl(attachment.url, attachment.contentType); - save({ - 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; - } + case 'media': { + const lightBoxOptions: LightBoxOptions = { + media: medias, + attachment: mediaItem.attachment, + }; - default: - throw new TypeError(`Unknown attachment type: '${type}'`); + onShowLightBox(lightBoxOptions); + 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; - const showAddRemoveModeratorsButton = isAdmin && !commonNoShow && isPublic; + default: + throw new TypeError(`Unknown attachment type: '${type}'`); + } + }; - const showUpdateGroupMembersButton = !isPublic && isGroup && !commonNoShow; + return { + media, + documents: _.compact(documents), // remove null + onItemClick, + }; +} - const deleteConvoAction = isPublic - ? () => { - deleteMessagesByConvoIdWithConfirmation(id); +// tslint:disable: cyclomatic-complexity +// tslint:disable: max-func-body-length +export const SessionRightPanelWithDetails = (props: Props) => { + const [documents, setDocuments] = useState>([]); + const [media, setMedia] = useState>([]); + const [onItemClick, setOnItemClick] = useState(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 ( -
- {this.renderHeader()} -

{name}

- {showMemberCount && ( - <> - -
- {window.i18n('members', memberCount)} -
- - - )} - - {showUpdateGroupNameButton && ( -
{ - await showUpdateGroupNameByConvoId(id); - }} - > - {isPublic ? window.i18n('editGroup') : window.i18n('editGroupName')} -
- )} - {showAddRemoveModeratorsButton && ( - <> -
{ - showAddModeratorsByConvoId(id); - }} - > - {window.i18n('addModerators')} -
-
{ - showRemoveModeratorsByConvoId(id); - }} - > - {window.i18n('removeModerators')} -
- - )} + }); + } - {showUpdateGroupMembersButton && ( -
{ - await showUpdateGroupMembersByConvoId(id); - }} - > - {window.i18n('groupMembers')} -
- )} - - {hasDisappearingMessages && ( - - )} - - - {isGroup && ( - // tslint:disable-next-line: use-simple-attributes - - )} -
- ); - } + return () => { + isRunning = false; + return; + }; + }, [props.isShowing, props.id]); + + useInterval(async () => { + if (props.isShowing) { + const results = await getMediaGalleryProps(props.id, media, props.onShowLightBox); + console.warn('results', results); + if (results.documents.length !== documents.length || results.media.length !== media.length) { + setDocuments(results.documents); + setMedia(results.media); + setOnItemClick(results.onItemClick); + } + } + }, 10000); - private renderHeader() { - const { - memberAvatars, - id, - onGoBack, - avatarPath, - isAdmin, - isPublic, - isKickedFromGroup, - isBlocked, - name, - profileName, - phoneNumber, - left, - } = this.props; + function renderHeader() { + const { memberAvatars, onGoBack, avatarPath, profileName, phoneNumber } = props; const showInviteContacts = (isPublic || isAdmin) && !isKickedFromGroup && !isBlocked && !left; const userName = name || profileName || phoneNumber; @@ -382,10 +240,10 @@ class SessionRightPanel extends React.Component { iconSize={SessionIconSize.Medium} iconRotation={270} onClick={onGoBack} - theme={this.props.theme} + theme={theme} /> { iconType={SessionIconType.AddUser} iconSize={SessionIconSize.Medium} onClick={() => { - showInviteContactByConvoId(this.props.id); + showInviteContactByConvoId(props.id); }} - theme={this.props.theme} + theme={theme} /> )}
); } -} -export const SessionRightPanelWithDetails = usingClosedConversationDetails( - withTheme(SessionRightPanel) -); + const { + 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 ( +
+ {renderHeader()} +

{name}

+ {showMemberCount && ( + <> + +
+ {window.i18n('members', memberCount)} +
+ + + )} + {showUpdateGroupNameButton && ( +
{ + await showUpdateGroupNameByConvoId(id); + }} + > + {isPublic ? window.i18n('editGroup') : window.i18n('editGroupName')} +
+ )} + {showAddRemoveModeratorsButton && ( + <> +
{ + showAddModeratorsByConvoId(id); + }} + > + {window.i18n('addModerators')} +
+
{ + showRemoveModeratorsByConvoId(id); + }} + > + {window.i18n('removeModerators')} +
+ + )} + + {showUpdateGroupMembersButton && ( +
{ + await showUpdateGroupMembersByConvoId(id); + }} + > + {window.i18n('groupMembers')} +
+ )} + + {hasDisappearingMessages && ( + + )} + + + {isGroup && ( + // tslint:disable-next-line: use-simple-attributes + + )} +
+ ); +}; diff --git a/ts/data/data.ts b/ts/data/data.ts index 2613be494..39985be5d 100644 --- a/ts/data/data.ts +++ b/ts/data/data.ts @@ -917,7 +917,7 @@ async function callChannel(name: string): Promise { export async function getMessagesWithVisualMediaAttachments( conversationId: string, options?: { limit: number } -): Promise { +): Promise> { return channels.getMessagesWithVisualMediaAttachments(conversationId, { limit: options?.limit, }); @@ -926,7 +926,7 @@ export async function getMessagesWithVisualMediaAttachments( export async function getMessagesWithFileAttachments( conversationId: string, options?: { limit: number } -): Promise { +): Promise> { return channels.getMessagesWithFileAttachments(conversationId, { limit: options?.limit, }); diff --git a/ts/models/conversation.ts b/ts/models/conversation.ts index b9a9bbdf0..2a23cbb90 100644 --- a/ts/models/conversation.ts +++ b/ts/models/conversation.ts @@ -892,7 +892,6 @@ export class ConversationModel extends Backbone.Model { const unreadCount = await this.getUnreadCount(); this.set({ unreadCount }); await this.commit(); - return model; } @@ -971,12 +970,7 @@ export class ConversationModel extends Backbone.Model { (m: any) => m.get('received_at') > newestUnreadDate ); const ourNumber = UserUtils.getOurPubKeyStrFromCache(); - return !stillUnread.some( - (m: any) => - m.propsForMessage && - m.propsForMessage.text && - m.propsForMessage.text.indexOf(`@${ourNumber}`) !== -1 - ); + return !stillUnread.some(m => m.getPropsForMessage()?.text?.indexOf(`@${ourNumber}`) !== -1); })(); if (mentionRead) { diff --git a/ts/models/message.ts b/ts/models/message.ts index 577ba4d44..64f79650c 100644 --- a/ts/models/message.ts +++ b/ts/models/message.ts @@ -27,6 +27,7 @@ import { LastMessageStatusType, MessageModelProps, MessagePropsDetails, + PropsForAttachment, PropsForExpirationTimer, PropsForGroupInvitation, PropsForGroupUpdate, @@ -54,6 +55,7 @@ import { getV2OpenGroupRoom } from '../data/opengroups'; import { getMessageController } from '../session/messages'; import { isUsFromCache } from '../session/utils/User'; import { perfEnd, perfStart } from '../session/utils/Performance'; +import { AttachmentType, AttachmentTypeWithPath } from '../types/Attachment'; export class MessageModel extends Backbone.Model { constructor(attributes: MessageAttributesOptionals) { @@ -94,7 +96,6 @@ export class MessageModel extends Backbone.Model { propsForTimerNotification: this.getPropsForTimerNotification(), }; perfEnd(`getPropsMessage-${this.id}`, 'getPropsMessage'); - return messageProps; } @@ -623,7 +624,7 @@ export class MessageModel extends Backbone.Model { const previews = this.get('preview') || []; return previews.map((preview: any) => { - let image = null; + let image: PropsForAttachment | null = null; try { if (preview.image) { image = this.getPropsForAttachment(preview.image); @@ -667,29 +668,39 @@ export class MessageModel extends Backbone.Model { }; } - public getPropsForAttachment(attachment: { - path?: string; - pending?: boolean; - flags: number; - size: number; - screenshot: any; - thumbnail: any; - }) { + public getPropsForAttachment(attachment: AttachmentTypeWithPath): PropsForAttachment | null { if (!attachment) { 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 { - ...attachment, + id: id ? `${id}` : undefined, + contentType, + size: size || 0, + width: width || 0, + height: height || 0, + path, + fileName, fileSize: size ? filesize(size) : null, - isVoiceMessage: - flags && - // eslint-disable-next-line no-bitwise - // tslint:disable-next-line: no-bitwise - flags & SignalService.AttachmentPointer.Flags.VOICE_MESSAGE, - pending, + isVoiceMessage, + pending: Boolean(pending), url: path ? window.Signal.Migrations.getAbsoluteAttachmentPath(path) : null, screenshot: screenshot ? { @@ -1140,18 +1151,23 @@ export class MessageModel extends Backbone.Model { } public isTrustedForAttachmentDownload() { - const senderConvoId = this.getSource(); - const isClosedGroup = this.getConversation()?.isClosedGroup() || false; - 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 - const senderConvo = getConversationController().get(senderConvoId); - if (!senderConvo) { + try { + const senderConvoId = this.getSource(); + const isClosedGroup = this.getConversation()?.isClosedGroup() || false; + 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 + 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 senderConvo.get('isTrustedForAttachmentDownload') || false; } } export class MessageCollection extends Backbone.Collection {} diff --git a/ts/models/messageType.ts b/ts/models/messageType.ts index 3b0790767..b4475a88b 100644 --- a/ts/models/messageType.ts +++ b/ts/models/messageType.ts @@ -228,7 +228,6 @@ export interface MessageRegularProps { authorProfileName?: string; authorName?: string; messageId?: string; - onClick: (data: any) => void; referencedMessageNotFound: boolean; }; previews: Array; @@ -261,5 +260,5 @@ export interface MessageRegularProps { playableMessageIndex?: number; nextMessageToPlay?: number; - playNextMessage?: (value: number) => any; + playNextMessage?: (value: number) => void; } diff --git a/ts/session/crypto/BufferPadding.ts b/ts/session/crypto/BufferPadding.ts index 6c7fbbda1..85c316c52 100644 --- a/ts/session/crypto/BufferPadding.ts +++ b/ts/session/crypto/BufferPadding.ts @@ -71,7 +71,7 @@ export function getUnpaddedAttachment( export function addAttachmentPadding(data: ArrayBuffer): ArrayBuffer { const originalUInt = new Uint8Array(data); - window?.log?.info('Adding attchment padding...'); + window?.log?.info('Adding attachment padding...'); const paddedSize = Math.max( 541, diff --git a/ts/session/messages/outgoing/controlMessage/DataExtractionNotificationMessage.ts b/ts/session/messages/outgoing/controlMessage/DataExtractionNotificationMessage.ts index 65a42b893..6c509a6da 100644 --- a/ts/session/messages/outgoing/controlMessage/DataExtractionNotificationMessage.ts +++ b/ts/session/messages/outgoing/controlMessage/DataExtractionNotificationMessage.ts @@ -46,16 +46,10 @@ export class DataExtractionNotificationMessage extends ContentMessage { export const sendDataExtractionNotification = async ( conversationId: string, attachmentSender: string, - referencedAttachmentTimestamp?: number + referencedAttachmentTimestamp: number ) => { const convo = getConversationController().get(conversationId); - if ( - !convo || - !convo.isPrivate() || - convo.isMe() || - UserUtils.isUsFromCache(PubKey.cast(attachmentSender)) || - !referencedAttachmentTimestamp - ) { + if (!convo || !convo.isPrivate() || convo.isMe() || UserUtils.isUsFromCache(attachmentSender)) { window.log.warn('Not sending saving attachment notification for', attachmentSender); return; } diff --git a/ts/session/snode_api/onions.ts b/ts/session/snode_api/onions.ts index 709bc1516..a2b879494 100644 --- a/ts/session/snode_api/onions.ts +++ b/ts/session/snode_api/onions.ts @@ -669,7 +669,8 @@ const sendOnionRequestHandlingSnodeEject = async ({ }): Promise => { // 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 - let response, decodingSymmetricKey; + let response; + let decodingSymmetricKey; try { // this might throw a timeout error const result = await sendOnionRequest({ diff --git a/ts/state/ducks/conversations.ts b/ts/state/ducks/conversations.ts index cee2ef625..b1b90652c 100644 --- a/ts/state/ducks/conversations.ts +++ b/ts/state/ducks/conversations.ts @@ -11,6 +11,7 @@ import { MessageModelType, PropsForDataExtractionNotification, } from '../../models/messageType'; +import { AttachmentType } from '../../types/Attachment'; export type MessageModelProps = { propsForMessage: PropsForMessage; @@ -101,6 +102,34 @@ export type PropsForSearchResults = { 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 = { text: string | null; id: string; @@ -114,7 +143,7 @@ export type PropsForMessage = { authorPhoneNumber: string; conversationType: ConversationTypeEnum; convoId: string; - attachments: any; + attachments: Array; previews: any; quote: any; authorAvatarPath: string | null; diff --git a/ts/test/components/media-gallery/groupMessagesByDate_test.ts b/ts/test/components/media-gallery/groupMessagesByDate_test.ts index e6ec4ceed..c78562770 100644 --- a/ts/test/components/media-gallery/groupMessagesByDate_test.ts +++ b/ts/test/components/media-gallery/groupMessagesByDate_test.ts @@ -15,20 +15,20 @@ const generatedMessageTimestamp = Date.now(); const toMediaItem = (date: Date): MediaItemType => ({ objectURL: date.toUTCString(), index: 0, - message: { - id: 'id', - received_at: date.getTime(), - sent_at: date.getTime(), - attachments: [], - }, attachment: { fileName: 'fileName', contentType: IMAGE_JPEG, url: 'url', + fileSize: null, + screenshot: null, + thumbnail: null, + path: '123456', + id: 123456, }, contentType: IMAGE_JPEG, messageSender: generatedMessageSenderKey, messageTimestamp: generatedMessageTimestamp, + messageId: '123456', }); // tslint:disable: max-func-body-length @@ -62,37 +62,37 @@ describe('groupMediaItemsByDate', () => { index: 0, contentType: IMAGE_JPEG, - message: { - id: 'id', - sent_at: 1523534400000, - received_at: 1523534400000, - attachments: [], - }, attachment: { fileName: 'fileName', contentType: IMAGE_JPEG, url: 'url', + fileSize: null, + screenshot: null, + thumbnail: null, + path: '123456', + id: 123456, }, messageSender: generatedMessageSenderKey, messageTimestamp: generatedMessageTimestamp, + messageId: '123456', }, { objectURL: 'Thu, 12 Apr 2018 00:01:00 GMT', index: 0, contentType: IMAGE_JPEG, - message: { - id: 'id', - received_at: 1523491260000, - sent_at: 1523491260000, - attachments: [], - }, attachment: { fileName: 'fileName', contentType: IMAGE_JPEG, url: 'url', + fileSize: null, + screenshot: null, + thumbnail: null, + path: '123456', + id: 123456, }, messageSender: generatedMessageSenderKey, messageTimestamp: generatedMessageTimestamp, + messageId: '123456', }, ], }, @@ -103,20 +103,19 @@ describe('groupMediaItemsByDate', () => { objectURL: 'Wed, 11 Apr 2018 23:59:00 GMT', index: 0, contentType: IMAGE_JPEG, - message: { - id: 'id', - received_at: 1523491140000, - sent_at: 1523491140000, - - attachments: [], - }, attachment: { fileName: 'fileName', contentType: IMAGE_JPEG, url: 'url', + fileSize: null, + screenshot: null, + thumbnail: null, + path: '123456', + id: 123456, }, messageSender: generatedMessageSenderKey, messageTimestamp: generatedMessageTimestamp, + messageId: '123456', }, ], }, @@ -127,20 +126,19 @@ describe('groupMediaItemsByDate', () => { objectURL: 'Mon, 09 Apr 2018 00:01:00 GMT', index: 0, contentType: IMAGE_JPEG, - message: { - id: 'id', - received_at: 1523232060000, - sent_at: 1523232060000, - - attachments: [], - }, attachment: { fileName: 'fileName', contentType: IMAGE_JPEG, url: 'url', + fileSize: null, + screenshot: null, + thumbnail: null, + path: '123456', + id: 123456, }, messageSender: generatedMessageSenderKey, messageTimestamp: generatedMessageTimestamp, + messageId: '123456', }, ], }, @@ -151,39 +149,37 @@ describe('groupMediaItemsByDate', () => { objectURL: 'Sun, 08 Apr 2018 23:59:00 GMT', index: 0, contentType: IMAGE_JPEG, - message: { - id: 'id', - received_at: 1523231940000, - sent_at: 1523231940000, - - attachments: [], - }, attachment: { fileName: 'fileName', contentType: IMAGE_JPEG, url: 'url', + fileSize: null, + screenshot: null, + thumbnail: null, + path: '123456', + id: 123456, }, messageSender: generatedMessageSenderKey, messageTimestamp: generatedMessageTimestamp, + messageId: '123456', }, { objectURL: 'Sun, 01 Apr 2018 00:01:00 GMT', index: 0, - message: { - id: 'id', - received_at: 1522540860000, - sent_at: 1522540860000, - - attachments: [], - }, contentType: IMAGE_JPEG, attachment: { fileName: 'fileName', contentType: IMAGE_JPEG, url: 'url', + fileSize: null, + screenshot: null, + thumbnail: null, + path: '123456', + id: 123456, }, messageSender: generatedMessageSenderKey, messageTimestamp: generatedMessageTimestamp, + messageId: '123456', }, ], }, @@ -196,39 +192,37 @@ describe('groupMediaItemsByDate', () => { objectURL: 'Sat, 31 Mar 2018 23:59:00 GMT', index: 0, contentType: IMAGE_JPEG, - message: { - id: 'id', - received_at: 1522540740000, - sent_at: 1522540740000, - - attachments: [], - }, attachment: { fileName: 'fileName', contentType: IMAGE_JPEG, url: 'url', + fileSize: null, + screenshot: null, + thumbnail: null, + path: '123456', + id: 123456, }, messageSender: generatedMessageSenderKey, messageTimestamp: generatedMessageTimestamp, + messageId: '123456', }, { objectURL: 'Thu, 01 Mar 2018 14:00:00 GMT', index: 0, contentType: IMAGE_JPEG, - message: { - id: 'id', - received_at: 1519912800000, - sent_at: 1519912800000, - - attachments: [], - }, attachment: { fileName: 'fileName', contentType: IMAGE_JPEG, url: 'url', + fileSize: null, + screenshot: null, + thumbnail: null, + path: '123456', + id: 123456, }, messageSender: generatedMessageSenderKey, messageTimestamp: generatedMessageTimestamp, + messageId: '123456', }, ], }, @@ -240,40 +234,38 @@ describe('groupMediaItemsByDate', () => { { objectURL: 'Mon, 28 Feb 2011 23:59:00 GMT', index: 0, - message: { - id: 'id', - received_at: 1298937540000, - sent_at: 1298937540000, - - attachments: [], - }, attachment: { fileName: 'fileName', contentType: IMAGE_JPEG, url: 'url', + fileSize: null, + screenshot: null, + thumbnail: null, + path: '123456', + id: 123456, }, contentType: IMAGE_JPEG, messageSender: generatedMessageSenderKey, messageTimestamp: generatedMessageTimestamp, + messageId: '123456', }, { objectURL: 'Tue, 01 Feb 2011 10:00:00 GMT', index: 0, contentType: IMAGE_JPEG, - message: { - id: 'id', - received_at: 1296554400000, - sent_at: 1296554400000, - - attachments: [], - }, messageSender: generatedMessageSenderKey, messageTimestamp: generatedMessageTimestamp, attachment: { fileName: 'fileName', contentType: IMAGE_JPEG, url: 'url', + fileSize: null, + screenshot: null, + thumbnail: null, + path: '123456', + id: 123456, }, + messageId: '123456', }, ], }, diff --git a/ts/test/types/Attachment_test.ts b/ts/test/types/Attachment_test.ts index a30fc03bc..a4602b758 100644 --- a/ts/test/types/Attachment_test.ts +++ b/ts/test/types/Attachment_test.ts @@ -15,6 +15,9 @@ describe('Attachment', () => { fileName: 'funny-cat.mov', url: 'funny-cat.mov', contentType: MIME.IMAGE_GIF, + fileSize: null, + screenshot: null, + thumbnail: null, }; assert.strictEqual(Attachment.getFileExtension(input), 'gif'); }); @@ -24,6 +27,9 @@ describe('Attachment', () => { fileName: 'funny-cat.mov', url: 'funny-cat.mov', contentType: MIME.VIDEO_QUICKTIME, + fileSize: null, + screenshot: null, + thumbnail: null, }; assert.strictEqual(Attachment.getFileExtension(input), 'mov'); }); @@ -33,6 +39,9 @@ describe('Attachment', () => { fileName: 'funny-cat.odt', url: 'funny-cat.odt', contentType: MIME.ODT, + fileSize: null, + screenshot: null, + thumbnail: null, }; assert.strictEqual(Attachment.getFileExtension(input), 'odt'); }); @@ -45,6 +54,9 @@ describe('Attachment', () => { fileName: 'funny-cat.mov', url: 'funny-cat.mov', contentType: MIME.VIDEO_QUICKTIME, + fileSize: null, + screenshot: null, + thumbnail: null, }; const actual = Attachment.getSuggestedFilename({ attachment }); const expected = 'funny-cat.mov'; @@ -55,6 +67,9 @@ describe('Attachment', () => { fileName: 'funny-cat.mov', url: 'funny-cat.mov', contentType: MIME.VIDEO_QUICKTIME, + fileSize: null, + screenshot: null, + thumbnail: null, }; const actual = Attachment.getSuggestedFilename({ attachment, @@ -68,6 +83,9 @@ describe('Attachment', () => { fileName: 'funny-cat.ini', url: 'funny-cat.ini', contentType: '', + fileSize: null, + screenshot: null, + thumbnail: null, }; const actual = Attachment.getSuggestedFilename({ attachment, @@ -82,6 +100,9 @@ describe('Attachment', () => { fileName: 'funny-cat.txt', url: 'funny-cat.txt', contentType: 'text/plain', + fileSize: null, + screenshot: null, + thumbnail: null, }; const actual = Attachment.getSuggestedFilename({ attachment, @@ -95,6 +116,9 @@ describe('Attachment', () => { fileName: 'funny-cat.json', url: 'funny-cat.json', contentType: '', + fileSize: null, + screenshot: null, + thumbnail: null, }; const actual = Attachment.getSuggestedFilename({ attachment, @@ -110,6 +134,9 @@ describe('Attachment', () => { contentType: MIME.VIDEO_QUICKTIME, url: 'funny-cat.mov', fileName: 'funny-cat.mov', + fileSize: null, + screenshot: null, + thumbnail: null, }; const timestamp = moment('2000-01-01').toDate(); const actual = Attachment.getSuggestedFilename({ @@ -126,6 +153,9 @@ describe('Attachment', () => { fileName: '', url: 'funny-cat.mov', contentType: MIME.VIDEO_QUICKTIME, + fileSize: null, + screenshot: null, + thumbnail: null, }; const timestamp = new Date(new Date(0).getTimezoneOffset() * 60 * 1000); const actual = Attachment.getSuggestedFilename({ @@ -142,6 +172,9 @@ describe('Attachment', () => { fileName: 'funny-cat.mov', url: 'funny-cat.mov', contentType: MIME.VIDEO_QUICKTIME, + fileSize: null, + screenshot: null, + thumbnail: null, }; const timestamp = new Date(new Date(0).getTimezoneOffset() * 60 * 1000); const actual = Attachment.getSuggestedFilename({ diff --git a/ts/types/Attachment.ts b/ts/types/Attachment.ts index e8aa6454c..3fdfb4bfe 100644 --- a/ts/types/Attachment.ts +++ b/ts/types/Attachment.ts @@ -26,22 +26,44 @@ export interface AttachmentType { url: string; videoUrl?: string; size?: number; - fileSize?: string; + fileSize: string | null; pending?: boolean; width?: number; height?: number; - screenshot?: { + screenshot: { height: number; width: number; url: string; contentType: MIME.MIMEType; - }; - thumbnail?: { + } | null; + thumbnail: { height: number; width: number; url: string; 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