Improve quoted attachment typings

This commit is contained in:
trevor-signal 2024-05-23 17:06:41 -04:00 committed by GitHub
parent 38226115a4
commit 5f0080a7d7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 98 additions and 97 deletions

View file

@ -37,7 +37,7 @@ import { ImageGrid } from './ImageGrid';
import { GIF } from './GIF'; import { GIF } from './GIF';
import { CurveType, Image } from './Image'; import { CurveType, Image } from './Image';
import { ContactName } from './ContactName'; import { ContactName } from './ContactName';
import type { QuotedAttachmentType } from './Quote'; import type { QuotedAttachmentForUIType } from './Quote';
import { Quote } from './Quote'; import { Quote } from './Quote';
import { EmbeddedContact } from './EmbeddedContact'; import { EmbeddedContact } from './EmbeddedContact';
import type { OwnProps as ReactionViewerProps } from './ReactionViewer'; import type { OwnProps as ReactionViewerProps } from './ReactionViewer';
@ -247,7 +247,7 @@ export type PropsData = {
conversationTitle: string; conversationTitle: string;
customColor?: CustomColorType; customColor?: CustomColorType;
text: string; text: string;
rawAttachment?: QuotedAttachmentType; rawAttachment?: QuotedAttachmentForUIType;
payment?: AnyPaymentEvent; payment?: AnyPaymentEvent;
isFromMe: boolean; isFromMe: boolean;
sentAt: number; sentAt: number;
@ -267,7 +267,7 @@ export type PropsData = {
customColor?: CustomColorType; customColor?: CustomColorType;
emoji?: string; emoji?: string;
isFromMe: boolean; isFromMe: boolean;
rawAttachment?: QuotedAttachmentType; rawAttachment?: QuotedAttachmentForUIType;
storyId?: string; storyId?: string;
text: string; text: string;
}; };

View file

@ -235,6 +235,7 @@ ImageOnly.args = {
contentType: IMAGE_PNG, contentType: IMAGE_PNG,
height: 100, height: 100,
width: 100, width: 100,
size: 100,
path: pngUrl, path: pngUrl,
objectUrl: pngUrl, objectUrl: pngUrl,
}, },
@ -251,6 +252,7 @@ ImageAttachment.args = {
contentType: IMAGE_PNG, contentType: IMAGE_PNG,
height: 100, height: 100,
width: 100, width: 100,
size: 100,
path: pngUrl, path: pngUrl,
objectUrl: pngUrl, objectUrl: pngUrl,
}, },
@ -287,6 +289,7 @@ VideoOnly.args = {
contentType: IMAGE_PNG, contentType: IMAGE_PNG,
height: 100, height: 100,
width: 100, width: 100,
size: 100,
path: pngUrl, path: pngUrl,
objectUrl: pngUrl, objectUrl: pngUrl,
}, },
@ -304,6 +307,7 @@ VideoAttachment.args = {
contentType: IMAGE_PNG, contentType: IMAGE_PNG,
height: 100, height: 100,
width: 100, width: 100,
size: 100,
path: pngUrl, path: pngUrl,
objectUrl: pngUrl, objectUrl: pngUrl,
}, },
@ -509,6 +513,7 @@ IsStoryReplyEmoji.args = {
contentType: IMAGE_PNG, contentType: IMAGE_PNG,
height: 100, height: 100,
width: 100, width: 100,
size: 100,
path: pngUrl, path: pngUrl,
objectUrl: pngUrl, objectUrl: pngUrl,
}, },

View file

@ -26,9 +26,13 @@ import type { AnyPaymentEvent } from '../../types/Payment';
import { PaymentEventKind } from '../../types/Payment'; import { PaymentEventKind } from '../../types/Payment';
import { getPaymentEventNotificationText } from '../../messages/helpers'; import { getPaymentEventNotificationText } from '../../messages/helpers';
import { RenderLocation } from './MessageTextRenderer'; import { RenderLocation } from './MessageTextRenderer';
import type { QuotedAttachmentType } from '../../model-types';
const EMPTY_OBJECT = Object.freeze(Object.create(null)); const EMPTY_OBJECT = Object.freeze(Object.create(null));
export type QuotedAttachmentForUIType = QuotedAttachmentType &
Pick<AttachmentType, 'isVoiceMessage' | 'fileName' | 'textAttachment'>;
export type Props = { export type Props = {
authorTitle: string; authorTitle: string;
conversationColor: ConversationColorType; conversationColor: ConversationColorType;
@ -44,7 +48,7 @@ export type Props = {
onClick?: () => void; onClick?: () => void;
onClose?: () => void; onClose?: () => void;
text: string; text: string;
rawAttachment?: QuotedAttachmentType; rawAttachment?: QuotedAttachmentForUIType;
payment?: AnyPaymentEvent; payment?: AnyPaymentEvent;
isGiftBadge: boolean; isGiftBadge: boolean;
isViewOnce: boolean; isViewOnce: boolean;
@ -53,11 +57,6 @@ export type Props = {
doubleCheckMissingQuoteReference?: () => unknown; doubleCheckMissingQuoteReference?: () => unknown;
}; };
export type QuotedAttachmentType = Pick<
AttachmentType,
'contentType' | 'fileName' | 'isVoiceMessage' | 'thumbnail' | 'textAttachment'
>;
function validateQuote(quote: Props): boolean { function validateQuote(quote: Props): boolean {
if ( if (
quote.isStoryReply && quote.isStoryReply &&
@ -86,9 +85,9 @@ function validateQuote(quote: Props): boolean {
} }
// Long message attachments should not be shown. // Long message attachments should not be shown.
function getAttachment( function getAttachment<T extends Pick<QuotedAttachmentType, 'contentType'>>(
rawAttachment: undefined | QuotedAttachmentType rawAttachment: T | undefined
): undefined | QuotedAttachmentType { ): T | undefined {
return rawAttachment && !MIME.isLongMessage(rawAttachment.contentType) return rawAttachment && !MIME.isLongMessage(rawAttachment.contentType)
? rawAttachment ? rawAttachment
: undefined; : undefined;

View file

@ -779,8 +779,7 @@ async function uploadMessageQuote({
prop: 'quote', prop: 'quote',
targetTimestamp, targetTimestamp,
}); });
const loadedQuote = const loadedQuote = await loadQuoteData(startingQuote);
message.cachedOutgoingQuoteData || (await loadQuoteData(startingQuote));
if (!loadedQuote) { if (!loadedQuote) {
return undefined; return undefined;
@ -797,7 +796,10 @@ async function uploadMessageQuote({
}; };
} }
const uploaded = await uploadAttachment(thumbnail); const { data } = thumbnail;
strictAssert(data, 'data must be loaded into thumbnail');
const uploaded = await uploadAttachment({ ...thumbnail, data });
return { return {
contentType: attachment.contentType, contentType: attachment.contentType,
@ -826,15 +828,11 @@ async function uploadMessageQuote({
} }
strictAssert( strictAssert(
attachment.path === loadedQuote.attachments.at(index)?.path, attachment.thumbnail.path ===
loadedQuote.attachments.at(index)?.thumbnail?.path,
`${logId}: Quote attachment ${index} was updated from under us` `${logId}: Quote attachment ${index} was updated from under us`
); );
strictAssert(
attachment.thumbnail,
`${logId}: Quote attachment ${index} no longer has a thumbnail`
);
const attachmentAfterThumbnailUpload = const attachmentAfterThumbnailUpload =
attachmentsAfterThumbnailUpload[index]; attachmentsAfterThumbnailUpload[index];
return { return {

View file

@ -274,7 +274,7 @@ export async function addAttachmentToMessage(
attachments: edit.quote.attachments.map(item => { attachments: edit.quote.attachments.map(item => {
const { thumbnail } = item; const { thumbnail } = item;
if (!thumbnail) { if (!thumbnail) {
return; return item;
} }
const newThumbnail = maybeReplaceAttachment(thumbnail); const newThumbnail = maybeReplaceAttachment(thumbnail);

13
ts/model-types.d.ts vendored
View file

@ -14,7 +14,11 @@ import type { ReadStatus } from './messages/MessageReadStatus';
import type { SendStateByConversationId } from './messages/MessageSendState'; import type { SendStateByConversationId } from './messages/MessageSendState';
import type { GroupNameCollisionsWithIdsByTitle } from './util/groupMemberNameCollisions'; import type { GroupNameCollisionsWithIdsByTitle } from './util/groupMemberNameCollisions';
import type { AttachmentDraftType, AttachmentType } from './types/Attachment'; import type {
AttachmentDraftType,
AttachmentType,
ThumbnailType,
} from './types/Attachment';
import type { EmbeddedContactType } from './types/EmbeddedContact'; import type { EmbeddedContactType } from './types/EmbeddedContact';
import { SignalService as Proto } from './protobuf'; import { SignalService as Proto } from './protobuf';
import type { AvatarDataType, ContactAvatarType } from './types/Avatar'; import type { AvatarDataType, ContactAvatarType } from './types/Avatar';
@ -73,16 +77,15 @@ export type GroupMigrationType = {
invitedMemberCount?: number; invitedMemberCount?: number;
}; };
export type QuotedAttachment = { export type QuotedAttachmentType = {
contentType: MIMEType; contentType: MIMEType;
fileName?: string; fileName?: string;
thumbnail?: AttachmentType; thumbnail?: ThumbnailType;
}; };
export type QuotedMessageType = { export type QuotedMessageType = {
// TODO DESKTOP-3826 // TODO DESKTOP-3826
// eslint-disable-next-line @typescript-eslint/no-explicit-any attachments: ReadonlyArray<QuotedAttachmentType>;
attachments: ReadonlyArray<any>;
payment?: AnyPaymentEvent; payment?: AnyPaymentEvent;
// `author` is an old attribute that holds the author's E164. We shouldn't use it for // `author` is an old attribute that holds the author's E164. We shouldn't use it for
// new messages, but old messages might have this attribute. // new messages, but old messages might have this attribute.

View file

@ -3967,7 +3967,6 @@ export class ConversationModel extends window.Backbone
}; };
}); });
} }
message.cachedOutgoingQuoteData = quote;
message.cachedOutgoingStickerData = sticker; message.cachedOutgoingStickerData = sticker;
const dbStart = Date.now(); const dbStart = Date.now();

View file

@ -37,7 +37,6 @@ import type {
} from '../textsecure/Types.d'; } from '../textsecure/Types.d';
import { SendMessageProtoError } from '../textsecure/Errors'; import { SendMessageProtoError } from '../textsecure/Errors';
import { getUserLanguages } from '../util/userLanguages'; import { getUserLanguages } from '../util/userLanguages';
import { copyCdnFields } from '../util/attachments';
import type { ReactionType } from '../types/Reactions'; import type { ReactionType } from '../types/Reactions';
import { ReactionReadStatus } from '../types/Reactions'; import { ReactionReadStatus } from '../types/Reactions';
@ -189,8 +188,6 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
cachedOutgoingPreviewData?: Array<LinkPreviewWithHydratedData>; cachedOutgoingPreviewData?: Array<LinkPreviewWithHydratedData>;
cachedOutgoingQuoteData?: QuotedMessageType;
cachedOutgoingStickerData?: StickerWithHydratedData; cachedOutgoingStickerData?: StickerWithHydratedData;
public registerLocations: Set<string>; public registerLocations: Set<string>;
@ -849,7 +846,6 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
// We aren't trying to send this message anymore, so we'll delete these caches // We aren't trying to send this message anymore, so we'll delete these caches
delete this.cachedOutgoingContactData; delete this.cachedOutgoingContactData;
delete this.cachedOutgoingPreviewData; delete this.cachedOutgoingPreviewData;
delete this.cachedOutgoingQuoteData;
delete this.cachedOutgoingStickerData; delete this.cachedOutgoingStickerData;
this.notifyStorySendFailed(); this.notifyStorySendFailed();
@ -1127,7 +1123,6 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
if (isTotalSuccess) { if (isTotalSuccess) {
delete this.cachedOutgoingContactData; delete this.cachedOutgoingContactData;
delete this.cachedOutgoingPreviewData; delete this.cachedOutgoingPreviewData;
delete this.cachedOutgoingQuoteData;
delete this.cachedOutgoingStickerData; delete this.cachedOutgoingStickerData;
} }
@ -1486,7 +1481,6 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
): Promise<void> { ): Promise<void> {
const { attachments } = quote; const { attachments } = quote;
const firstAttachment = attachments ? attachments[0] : undefined; const firstAttachment = attachments ? attachments[0] : undefined;
const firstThumbnailCdnFields = copyCdnFields(firstAttachment?.thumbnail);
if (messageHasPaymentEvent(originalMessage.attributes)) { if (messageHasPaymentEvent(originalMessage.attributes)) {
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
@ -1535,7 +1529,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
quote.bodyRanges = originalMessage.attributes.bodyRanges; quote.bodyRanges = originalMessage.attributes.bodyRanges;
if (firstAttachment) { if (firstAttachment) {
firstAttachment.thumbnail = null; firstAttachment.thumbnail = undefined;
} }
if (!firstAttachment || !firstAttachment.contentType) { if (!firstAttachment || !firstAttachment.contentType) {
@ -1571,14 +1565,13 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
if (thumbnail && thumbnail.path) { if (thumbnail && thumbnail.path) {
firstAttachment.thumbnail = { firstAttachment.thumbnail = {
...firstThumbnailCdnFields,
...thumbnail, ...thumbnail,
copied: true, copied: true,
}; };
} else { } else {
firstAttachment.contentType = queryFirst.contentType; firstAttachment.contentType = queryFirst.contentType;
firstAttachment.fileName = queryFirst.fileName; firstAttachment.fileName = queryFirst.fileName;
firstAttachment.thumbnail = null; firstAttachment.thumbnail = undefined;
} }
} }
@ -1589,7 +1582,6 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
if (image && image.path) { if (image && image.path) {
firstAttachment.thumbnail = { firstAttachment.thumbnail = {
...firstThumbnailCdnFields,
...image, ...image,
copied: true, copied: true,
}; };
@ -1599,7 +1591,6 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
const sticker = originalMessage.get('sticker'); const sticker = originalMessage.get('sticker');
if (sticker && sticker.data && sticker.data.path) { if (sticker && sticker.data && sticker.data.path) {
firstAttachment.thumbnail = { firstAttachment.thumbnail = {
...firstThumbnailCdnFields,
...sticker.data, ...sticker.data,
copied: true, copied: true,
}; };

View file

@ -24,7 +24,7 @@ import { PaymentEventKind } from '../../types/Payment';
import type { import type {
ConversationAttributesType, ConversationAttributesType,
MessageAttributesType, MessageAttributesType,
QuotedAttachment, QuotedAttachmentType,
QuotedMessageType, QuotedMessageType,
} from '../../model-types.d'; } from '../../model-types.d';
import { drop } from '../../util/drop'; import { drop } from '../../util/drop';
@ -1622,7 +1622,7 @@ export class BackupExportStream extends Readable {
: null, : null,
authorId, authorId,
text: quote.text, text: quote.text,
attachments: quote.attachments.map((attachment: QuotedAttachment) => { attachments: quote.attachments.map((attachment: QuotedAttachmentType) => {
return { return {
contentType: attachment.contentType, contentType: attachment.contentType,
fileName: attachment.fileName, fileName: attachment.fileName,

View file

@ -13,6 +13,7 @@ import type {
LastMessageStatus, LastMessageStatus,
MessageAttributesType, MessageAttributesType,
MessageReactionType, MessageReactionType,
QuotedAttachmentType,
ShallowChallengeError, ShallowChallengeError,
} from '../../model-types.d'; } from '../../model-types.d';
@ -41,7 +42,6 @@ import type {
ChangeType, ChangeType,
} from '../../components/conversation/GroupNotification'; } from '../../components/conversation/GroupNotification';
import type { PropsType as ProfileChangeNotificationPropsType } from '../../components/conversation/ProfileChangeNotification'; import type { PropsType as ProfileChangeNotificationPropsType } from '../../components/conversation/ProfileChangeNotification';
import type { QuotedAttachmentType } from '../../components/conversation/Quote';
import { getDomain, isCallLink, isStickerPack } from '../../types/LinkPreview'; import { getDomain, isCallLink, isStickerPack } from '../../types/LinkPreview';
import type { import type {
@ -1852,9 +1852,7 @@ export function getPropsForAttachment(
}; };
} }
function processQuoteAttachment( function processQuoteAttachment(attachment: QuotedAttachmentType) {
attachment: AttachmentType
): QuotedAttachmentType {
const { thumbnail } = attachment; const { thumbnail } = attachment;
const path = const path =
thumbnail && thumbnail.path && getAttachmentUrlForPath(thumbnail.path); thumbnail && thumbnail.path && getAttachmentUrlForPath(thumbnail.path);

View file

@ -26,6 +26,7 @@ export const fakeThumbnail = (url: string): ThumbnailType => ({
path: url, path: url,
url, url,
width: 100, width: 100,
size: 128,
}); });
export const fakeDraftAttachment = ( export const fakeDraftAttachment = (

View file

@ -10,7 +10,7 @@ import {
} from '../textsecure/processDataMessage'; } from '../textsecure/processDataMessage';
import type { ProcessedAttachment } from '../textsecure/Types.d'; import type { ProcessedAttachment } from '../textsecure/Types.d';
import { SignalService as Proto } from '../protobuf'; import { SignalService as Proto } from '../protobuf';
import { IMAGE_GIF } from '../types/MIME'; import { IMAGE_GIF, IMAGE_JPEG } from '../types/MIME';
import { generateAci } from '../types/ServiceId'; import { generateAci } from '../types/ServiceId';
const ACI_1 = generateAci(); const ACI_1 = generateAci();
@ -142,7 +142,7 @@ describe('processDataMessage', () => {
text: 'text', text: 'text',
attachments: [ attachments: [
{ {
contentType: 'image/jpeg', contentType: IMAGE_JPEG,
fileName: 'image.jpg', fileName: 'image.jpg',
thumbnail: PROCESSED_ATTACHMENT, thumbnail: PROCESSED_ATTACHMENT,
}, },

View file

@ -174,9 +174,12 @@ describe('Message', () => {
referencedMessageNotFound: false, referencedMessageNotFound: false,
attachments: [ attachments: [
{ {
contentType: MIME.APPLICATION_OCTET_STREAM,
thumbnail: { thumbnail: {
path: 'ab/abcdefghi', path: 'ab/abcdefghi',
data: Bytes.fromString('Its easy if you try'), data: Bytes.fromString('Its easy if you try'),
contentType: MIME.APPLICATION_OCTET_STREAM,
size: 128,
}, },
}, },
], ],
@ -193,8 +196,11 @@ describe('Message', () => {
referencedMessageNotFound: false, referencedMessageNotFound: false,
attachments: [ attachments: [
{ {
contentType: MIME.APPLICATION_OCTET_STREAM,
thumbnail: { thumbnail: {
path: 'ab/abcdefghi', path: 'ab/abcdefghi',
contentType: MIME.APPLICATION_OCTET_STREAM,
size: 128,
}, },
}, },
], ],
@ -707,7 +713,7 @@ describe('Message', () => {
attachments: [ attachments: [
{ {
fileName: 'manifesto.txt', fileName: 'manifesto.txt',
contentType: 'text/plain', contentType: MIME.TEXT_ATTACHMENT,
}, },
], ],
id: 34233, id: 34233,
@ -726,7 +732,7 @@ describe('Message', () => {
it('does not eliminate thumbnails with missing data field', async () => { it('does not eliminate thumbnails with missing data field', async () => {
const upgradeAttachment = sinon const upgradeAttachment = sinon
.stub() .stub()
.returns({ fileName: 'processed!' }); .returns({ contentType: MIME.IMAGE_GIF, size: 42 });
const upgradeVersion = Message._mapQuotedAttachments(upgradeAttachment); const upgradeVersion = Message._mapQuotedAttachments(upgradeAttachment);
const message = getDefaultMessage({ const message = getDefaultMessage({
@ -736,9 +742,10 @@ describe('Message', () => {
attachments: [ attachments: [
{ {
fileName: 'cat.gif', fileName: 'cat.gif',
contentType: 'image/gif', contentType: MIME.IMAGE_GIF,
thumbnail: { thumbnail: {
fileName: 'not yet downloaded!', contentType: MIME.IMAGE_GIF,
size: 128,
}, },
}, },
], ],
@ -754,10 +761,11 @@ describe('Message', () => {
text: 'hey!', text: 'hey!',
attachments: [ attachments: [
{ {
contentType: 'image/gif', contentType: MIME.IMAGE_GIF,
fileName: 'cat.gif', fileName: 'cat.gif',
thumbnail: { thumbnail: {
fileName: 'processed!', contentType: MIME.IMAGE_GIF,
size: 42,
}, },
}, },
], ],
@ -777,6 +785,8 @@ describe('Message', () => {
it('calls provided async function for each quoted attachment', async () => { it('calls provided async function for each quoted attachment', async () => {
const upgradeAttachment = sinon.stub().resolves({ const upgradeAttachment = sinon.stub().resolves({
path: '/new/path/on/disk', path: '/new/path/on/disk',
contentType: MIME.TEXT_ATTACHMENT,
size: 100,
}); });
const upgradeVersion = Message._mapQuotedAttachments(upgradeAttachment); const upgradeVersion = Message._mapQuotedAttachments(upgradeAttachment);
@ -786,8 +796,11 @@ describe('Message', () => {
text: 'hey!', text: 'hey!',
attachments: [ attachments: [
{ {
contentType: MIME.TEXT_ATTACHMENT,
thumbnail: { thumbnail: {
data: 'data is here', contentType: MIME.TEXT_ATTACHMENT,
size: 100,
data: Buffer.from('data is here'),
}, },
}, },
], ],
@ -803,7 +816,10 @@ describe('Message', () => {
text: 'hey!', text: 'hey!',
attachments: [ attachments: [
{ {
contentType: MIME.TEXT_ATTACHMENT,
thumbnail: { thumbnail: {
contentType: MIME.TEXT_ATTACHMENT,
size: 100,
path: '/new/path/on/disk', path: '/new/path/on/disk',
}, },
}, },

View file

@ -132,7 +132,7 @@ export type ProcessedGroupV2Context = {
}; };
export type ProcessedQuoteAttachment = { export type ProcessedQuoteAttachment = {
contentType?: string; contentType: MIMEType;
fileName?: string; fileName?: string;
thumbnail?: ProcessedAttachment; thumbnail?: ProcessedAttachment;
}; };

View file

@ -142,7 +142,9 @@ export function processQuote(
text: dropNull(quote.text), text: dropNull(quote.text),
attachments: (quote.attachments ?? []).map(attachment => { attachments: (quote.attachments ?? []).map(attachment => {
return { return {
contentType: dropNull(attachment.contentType), contentType: attachment.contentType
? stringToMIMEType(attachment.contentType)
: APPLICATION_OCTET_STREAM,
fileName: dropNull(attachment.fileName), fileName: dropNull(attachment.fileName),
thumbnail: processAttachment(attachment.thumbnail), thumbnail: processAttachment(attachment.thumbnail),
}; };

View file

@ -185,12 +185,11 @@ export type AttachmentDraftType =
size: number; size: number;
}; };
export type ThumbnailType = Pick< export type ThumbnailType = AttachmentType & {
AttachmentType,
'height' | 'width' | 'url' | 'contentType' | 'path' | 'data'
> & {
// Only used when quote needed to make an in-memory thumbnail // Only used when quote needed to make an in-memory thumbnail
objectUrl?: string; objectUrl?: string;
// Whether the thumbnail has been copied from the original (quoted) message
copied?: boolean;
}; };
// // Incoming message attachment fields // // Incoming message attachment fields
@ -452,6 +451,7 @@ export async function captureDimensionsAndScreenshot(
contentType: THUMBNAIL_CONTENT_TYPE, contentType: THUMBNAIL_CONTENT_TYPE,
width: THUMBNAIL_SIZE, width: THUMBNAIL_SIZE,
height: THUMBNAIL_SIZE, height: THUMBNAIL_SIZE,
size: 100,
}, },
}; };
} catch (error) { } catch (error) {
@ -511,6 +511,7 @@ export async function captureDimensionsAndScreenshot(
contentType: THUMBNAIL_CONTENT_TYPE, contentType: THUMBNAIL_CONTENT_TYPE,
width: THUMBNAIL_SIZE, width: THUMBNAIL_SIZE,
height: THUMBNAIL_SIZE, height: THUMBNAIL_SIZE,
size: 100,
}, },
width, width,
height, height,
@ -873,7 +874,9 @@ export const isFile = (attachment: AttachmentType): boolean => {
return true; return true;
}; };
export const isVoiceMessage = (attachment: AttachmentType): boolean => { export const isVoiceMessage = (
attachment: Pick<AttachmentType, 'contentType' | 'fileName' | 'flags'>
): boolean => {
const flag = SignalService.AttachmentPointer.Flags.VOICE_MESSAGE; const flag = SignalService.AttachmentPointer.Flags.VOICE_MESSAGE;
const hasFlag = const hasFlag =
// eslint-disable-next-line no-bitwise // eslint-disable-next-line no-bitwise

View file

@ -25,6 +25,7 @@ import type {
import type { import type {
MessageAttributesType, MessageAttributesType,
QuotedAttachmentType,
QuotedMessageType, QuotedMessageType,
} from '../model-types.d'; } from '../model-types.d';
import type { import type {
@ -309,8 +310,8 @@ export const _mapQuotedAttachments =
} }
const upgradeWithContext = async ( const upgradeWithContext = async (
attachment: AttachmentType attachment: QuotedAttachmentType
): Promise<AttachmentType> => { ): Promise<QuotedAttachmentType> => {
const { thumbnail } = attachment; const { thumbnail } = attachment;
if (!thumbnail) { if (!thumbnail) {
return attachment; return attachment;
@ -995,7 +996,7 @@ export const createAttachmentDataWriter = ({
} }
}); });
const writeQuoteAttachment = async (attachment: AttachmentType) => { const writeQuoteAttachment = async (attachment: QuotedAttachmentType) => {
const { thumbnail } = attachment; const { thumbnail } = attachment;
if (!thumbnail) { if (!thumbnail) {
return attachment; return attachment;

View file

@ -6,6 +6,7 @@ import type { EditAttributesType } from '../messageModifiers/Edits';
import type { import type {
EditHistoryType, EditHistoryType,
MessageAttributesType, MessageAttributesType,
QuotedAttachmentType,
QuotedMessageType, QuotedMessageType,
} from '../model-types.d'; } from '../model-types.d';
import type { LinkPreviewType } from '../types/message/LinkPreviews'; import type { LinkPreviewType } from '../types/message/LinkPreviews';
@ -143,7 +144,7 @@ export async function handleEditMessage(
// and they have already been downloaded. // and they have already been downloaded.
const attachmentSignatures: Map<string, AttachmentType> = new Map(); const attachmentSignatures: Map<string, AttachmentType> = new Map();
const previewSignatures: Map<string, LinkPreviewType> = new Map(); const previewSignatures: Map<string, LinkPreviewType> = new Map();
const quoteSignatures: Map<string, AttachmentType> = new Map(); const quoteSignatures: Map<string, QuotedAttachmentType> = new Map();
mainMessage.attachments?.forEach(attachment => { mainMessage.attachments?.forEach(attachment => {
const signature = getAttachmentSignatureSafe(attachment); const signature = getAttachmentSignatureSafe(attachment);
@ -226,13 +227,13 @@ export async function handleEditMessage(
return attachment; return attachment;
} }
const signature = getAttachmentSignatureSafe(attachment.thumbnail); const signature = getAttachmentSignatureSafe(attachment.thumbnail);
const existingThumbnail = signature const existingQuoteAttachment = signature
? quoteSignatures.get(signature) ? quoteSignatures.get(signature)
: undefined; : undefined;
if (existingThumbnail) { if (existingQuoteAttachment) {
return { return {
...attachment, ...attachment,
thumbnail: existingThumbnail, thumbnail: existingQuoteAttachment.thumbnail,
}; };
} }

View file

@ -1,12 +1,12 @@
// Copyright 2023 Signal Messenger, LLC // Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import type { AttachmentType, ThumbnailType } from '../types/Attachment'; import type { AttachmentType } from '../types/Attachment';
import type { import type {
MessageAttributesType, MessageAttributesType,
QuotedAttachmentType,
QuotedMessageType, QuotedMessageType,
} from '../model-types.d'; } from '../model-types.d';
import type { MIMEType } from '../types/MIME';
import type { LinkPreviewType } from '../types/message/LinkPreviews'; import type { LinkPreviewType } from '../types/message/LinkPreviews';
import type { StickerType } from '../types/Stickers'; import type { StickerType } from '../types/Stickers';
import { IMAGE_JPEG, IMAGE_GIF } from '../types/MIME'; import { IMAGE_JPEG, IMAGE_GIF } from '../types/MIME';
@ -40,7 +40,7 @@ export async function makeQuote(
return { return {
authorAci: contact.getCheckedAci('makeQuote'), authorAci: contact.getCheckedAci('makeQuote'),
attachments: isTapToView(quotedMessage) attachments: isTapToView(quotedMessage)
? [{ contentType: IMAGE_JPEG, fileName: null }] ? [{ contentType: IMAGE_JPEG }]
: await getQuoteAttachment(attachments, preview, sticker), : await getQuoteAttachment(attachments, preview, sticker),
payment, payment,
bodyRanges, bodyRanges,
@ -57,13 +57,7 @@ export async function getQuoteAttachment(
attachments?: Array<AttachmentType>, attachments?: Array<AttachmentType>,
preview?: Array<LinkPreviewType>, preview?: Array<LinkPreviewType>,
sticker?: StickerType sticker?: StickerType
): Promise< ): Promise<Array<QuotedAttachmentType>> {
Array<{
contentType: MIMEType;
fileName?: string | null;
thumbnail?: ThumbnailType | null;
}>
> {
const { getAbsoluteAttachmentPath, loadAttachmentData } = const { getAbsoluteAttachmentPath, loadAttachmentData } =
window.Signal.Migrations; window.Signal.Migrations;
@ -78,18 +72,14 @@ export async function getQuoteAttachment(
if (!path) { if (!path) {
return { return {
contentType: isGIFQuote ? IMAGE_GIF : contentType, contentType: isGIFQuote ? IMAGE_GIF : contentType,
// Our protos library complains about this field being undefined, so we fileName,
// force it to null thumbnail,
fileName: fileName || null,
thumbnail: null,
}; };
} }
return { return {
contentType: isGIFQuote ? IMAGE_GIF : contentType, contentType: isGIFQuote ? IMAGE_GIF : contentType,
// Our protos library complains about this field being undefined, so we force fileName,
// it to null
fileName: fileName || null,
thumbnail: thumbnail thumbnail: thumbnail
? { ? {
...(await loadAttachmentData(thumbnail)), ...(await loadAttachmentData(thumbnail)),
@ -97,7 +87,7 @@ export async function getQuoteAttachment(
? getAbsoluteAttachmentPath(thumbnail.path) ? getAbsoluteAttachmentPath(thumbnail.path)
: undefined, : undefined,
} }
: null, : undefined,
}; };
}) })
); );
@ -113,9 +103,6 @@ export async function getQuoteAttachment(
return { return {
contentType, contentType,
// Our protos library complains about this field being undefined, so we
// force it to null
fileName: null,
thumbnail: image thumbnail: image
? { ? {
...(await loadAttachmentData(image)), ...(await loadAttachmentData(image)),
@ -123,7 +110,7 @@ export async function getQuoteAttachment(
? getAbsoluteAttachmentPath(image.path) ? getAbsoluteAttachmentPath(image.path)
: undefined, : undefined,
} }
: null, : undefined,
}; };
}) })
); );
@ -135,9 +122,6 @@ export async function getQuoteAttachment(
return [ return [
{ {
contentType, contentType,
// Our protos library complains about this field being undefined, so we
// force it to null
fileName: null,
thumbnail: { thumbnail: {
...(await loadAttachmentData(sticker.data)), ...(await loadAttachmentData(sticker.data)),
objectUrl: path ? getAbsoluteAttachmentPath(path) : undefined, objectUrl: path ? getAbsoluteAttachmentPath(path) : undefined,

View file

@ -12,7 +12,7 @@ import {
} from '../types/Stickers'; } from '../types/Stickers';
import dataInterface from '../sql/Client'; import dataInterface from '../sql/Client';
import type { AttachmentType } from '../types/Attachment'; import type { AttachmentType, ThumbnailType } from '../types/Attachment';
import type { EmbeddedContactType } from '../types/EmbeddedContact'; import type { EmbeddedContactType } from '../types/EmbeddedContact';
import type { import type {
EditHistoryType, EditHistoryType,
@ -540,17 +540,17 @@ async function queueQuoteAttachments({
// Similar to queueNormalAttachments' logic for detecting same attachments // Similar to queueNormalAttachments' logic for detecting same attachments
// except here we also pick by quote sent timestamp. // except here we also pick by quote sent timestamp.
const thumbnailSignatures: Map<string, AttachmentType> = new Map(); const thumbnailSignatures: Map<string, ThumbnailType> = new Map();
otherQuotes.forEach(otherQuote => { otherQuotes.forEach(otherQuote => {
for (const attachment of otherQuote.attachments) { for (const attachment of otherQuote.attachments) {
const signature = getQuoteThumbnailSignature( const signature = getQuoteThumbnailSignature(
otherQuote, otherQuote,
attachment.thumbnail attachment.thumbnail
); );
if (!signature) { if (!signature || !attachment.thumbnail) {
continue; continue;
} }
thumbnailSignatures.set(signature, attachment); thumbnailSignatures.set(signature, attachment.thumbnail);
} }
}); });