Improve quoted attachment typings
This commit is contained in:
parent
38226115a4
commit
5f0080a7d7
20 changed files with 98 additions and 97 deletions
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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,
|
||||||
},
|
},
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
13
ts/model-types.d.ts
vendored
|
@ -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.
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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 = (
|
||||||
|
|
|
@ -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,
|
||||||
},
|
},
|
||||||
|
|
|
@ -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('It’s easy if you try'),
|
data: Bytes.fromString('It’s 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',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
2
ts/textsecure/Types.d.ts
vendored
2
ts/textsecure/Types.d.ts
vendored
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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),
|
||||||
};
|
};
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue