diff --git a/ts/jobs/AttachmentDownloadManager.ts b/ts/jobs/AttachmentDownloadManager.ts index a24987cedaa..89b66b85fd5 100644 --- a/ts/jobs/AttachmentDownloadManager.ts +++ b/ts/jobs/AttachmentDownloadManager.ts @@ -7,7 +7,7 @@ import * as durations from '../util/durations/index.js'; import { createLogger } from '../logging/log.js'; import type { AttachmentBackfillResponseSyncEvent } from '../textsecure/messageReceiverEvents.js'; import { - type AttachmentDownloadJobTypeType, + type MessageAttachmentType, type AttachmentDownloadJobType, type CoreAttachmentDownloadJobType, AttachmentDownloadUrgency, @@ -82,7 +82,7 @@ export { isPermanentlyUndownloadable }; // Type for adding a new job export type NewAttachmentDownloadJobType = { attachment: AttachmentType; - attachmentType: AttachmentDownloadJobTypeType; + attachmentType: MessageAttachmentType; isManualDownload: boolean; messageId: string; receivedAt: number; @@ -808,10 +808,13 @@ export async function runDownloadAttachmentJobInner({ }, }); - const upgradedAttachment = await dependencies.processNewAttachment({ - ...omit(attachment, ['error', 'pending']), - ...downloadedAttachment, - }); + const upgradedAttachment = await dependencies.processNewAttachment( + { + ...omit(attachment, ['error', 'pending']), + ...downloadedAttachment, + }, + attachmentType + ); const isShowingLightbox = (): boolean => { const lightboxState = window.reduxStore.getState().lightbox; diff --git a/ts/jobs/helpers/attachmentBackfill.ts b/ts/jobs/helpers/attachmentBackfill.ts index f656984adb2..c6948218b23 100644 --- a/ts/jobs/helpers/attachmentBackfill.ts +++ b/ts/jobs/helpers/attachmentBackfill.ts @@ -13,7 +13,7 @@ import { getUndownloadedAttachmentSignature, } from '../../types/Attachment.js'; import { - type AttachmentDownloadJobTypeType, + type MessageAttachmentType, AttachmentDownloadUrgency, } from '../../types/AttachmentDownload.js'; import { AttachmentDownloadSource } from '../../sql/Interface.js'; @@ -353,7 +353,7 @@ export class AttachmentBackfill { } public static isEnabledForJob( - jobType: AttachmentDownloadJobTypeType, + jobType: MessageAttachmentType, message: Pick ): boolean { if (message.type === 'story') { @@ -456,7 +456,7 @@ export class AttachmentBackfill { export function isPermanentlyUndownloadable( attachment: AttachmentType, - disposition: AttachmentDownloadJobTypeType, + disposition: MessageAttachmentType, message: Pick ): boolean { // Attachment is downloadable or user have not failed to download it yet diff --git a/ts/messageModifiers/AttachmentDownloads.ts b/ts/messageModifiers/AttachmentDownloads.ts index fd92c92796a..a8d204e2092 100644 --- a/ts/messageModifiers/AttachmentDownloads.ts +++ b/ts/messageModifiers/AttachmentDownloads.ts @@ -3,7 +3,7 @@ import lodash from 'lodash'; import { createLogger } from '../logging/log.js'; import * as Bytes from '../Bytes.js'; -import type { AttachmentDownloadJobTypeType } from '../types/AttachmentDownload.js'; +import type { MessageAttachmentType } from '../types/AttachmentDownload.js'; import type { AttachmentType } from '../types/Attachment.js'; import { @@ -69,7 +69,7 @@ export async function addAttachmentToMessage( messageId: string, attachment: AttachmentType, jobLogId: string, - { type }: { type: AttachmentDownloadJobTypeType } + { type }: { type: MessageAttachmentType } ): Promise { const logPrefix = `${jobLogId}/addAttachmentToMessage`; const message = await getMessageById(messageId); diff --git a/ts/services/releaseNotesFetcher.ts b/ts/services/releaseNotesFetcher.ts index a26a6e28fa4..8a4da630745 100644 --- a/ts/services/releaseNotesFetcher.ts +++ b/ts/services/releaseNotesFetcher.ts @@ -245,10 +245,13 @@ export class ReleaseNotesFetcher { ); const processedAttachment = - await window.Signal.Migrations.processNewAttachment({ - ...localAttachment, - contentType: stringToMIMEType(contentType), - }); + await window.Signal.Migrations.processNewAttachment( + { + ...localAttachment, + contentType: stringToMIMEType(contentType), + }, + 'attachment' + ); return { hydratedNote, processedAttachment }; } diff --git a/ts/signal.ts b/ts/signal.ts index 72e66eb9697..5b1fd1e1cdb 100644 --- a/ts/signal.ts +++ b/ts/signal.ts @@ -58,6 +58,7 @@ import type { } from './types/message/LinkPreviews.js'; import type { StickerType, StickerWithHydratedData } from './types/Stickers.js'; import { beforeNavigateService } from './services/BeforeNavigate.js'; +import type { MessageAttachmentType } from './types/AttachmentDownload.js'; type EncryptedReader = ( attachment: Partial @@ -122,7 +123,10 @@ type MigrationsModuleType = { name: string; baseDir?: string; }) => Promise; - processNewAttachment: (attachment: AttachmentType) => Promise; + processNewAttachment: ( + attachment: AttachmentType, + attachmentType: MessageAttachmentType + ) => Promise; processNewSticker: (stickerData: Uint8Array) => Promise< LocalAttachmentV2Type & { width: number; @@ -327,8 +331,11 @@ export function initializeMigrations({ readStickerData, readTempData, saveAttachmentToDisk, - processNewAttachment: (attachment: AttachmentType) => - MessageType.processNewAttachment(attachment, { + processNewAttachment: ( + attachment: AttachmentType, + attachmentType: MessageAttachmentType + ) => + MessageType.processNewAttachment(attachment, attachmentType, { writeNewAttachmentData, makeObjectUrl, revokeObjectUrl, diff --git a/ts/sql/Interface.ts b/ts/sql/Interface.ts index 2b0bd26ee58..f8bcd95201e 100644 --- a/ts/sql/Interface.ts +++ b/ts/sql/Interface.ts @@ -49,7 +49,7 @@ import type { } from '../types/CallLink.js'; import type { AttachmentDownloadJobType, - AttachmentDownloadJobTypeType, + MessageAttachmentType, } from '../types/AttachmentDownload.js'; import type { GroupSendEndorsementsData, @@ -651,7 +651,7 @@ export const MESSAGE_ATTACHMENT_COLUMNS = [ export type MessageAttachmentDBType = { messageId: string; - attachmentType: AttachmentDownloadJobTypeType; + attachmentType: MessageAttachmentType; orderInMessage: number; editHistoryIndex: number | null; conversationId: string; diff --git a/ts/sql/Server.ts b/ts/sql/Server.ts index 604aa39e0a1..844137345a7 100644 --- a/ts/sql/Server.ts +++ b/ts/sql/Server.ts @@ -86,7 +86,7 @@ import { } from '../types/AttachmentBackup.js'; import { attachmentDownloadJobSchema, - type AttachmentDownloadJobTypeType, + type MessageAttachmentType, type AttachmentDownloadJobType, } from '../types/AttachmentDownload.js'; import type { @@ -2700,7 +2700,7 @@ function saveMessageAttachment({ sentAt: number; receivedAt: number; receivedAtMs: number | undefined; - attachmentType: AttachmentDownloadJobTypeType; + attachmentType: MessageAttachmentType; attachment: AttachmentType; orderInMessage: number; editHistoryIndex: number | null; diff --git a/ts/sql/migrations/1040-undownloaded-backed-up-media.ts b/ts/sql/migrations/1040-undownloaded-backed-up-media.ts index 7472d586a4e..61dcc947704 100644 --- a/ts/sql/migrations/1040-undownloaded-backed-up-media.ts +++ b/ts/sql/migrations/1040-undownloaded-backed-up-media.ts @@ -6,9 +6,9 @@ import * as z from 'zod'; import type { LoggerType } from '../../types/Logging.js'; import { - attachmentDownloadTypeSchema, + messageAttachmentTypeSchema, type AttachmentDownloadJobType, - type AttachmentDownloadJobTypeType, + type MessageAttachmentType, } from '../../types/AttachmentDownload.js'; import type { AttachmentType } from '../../types/Attachment.js'; import { jsonToObject, objectToJSON, sql } from '../util.js'; @@ -28,7 +28,7 @@ export type _AttachmentDownloadJobTypeV1030 = { messageId: string; pending: number; timestamp: number; - type: AttachmentDownloadJobTypeType; + type: MessageAttachmentType; }; const attachmentDownloadJobSchemaV1040 = z @@ -36,7 +36,7 @@ const attachmentDownloadJobSchemaV1040 = z attachment: z .object({ size: z.number(), contentType: MIMETypeSchema }) .passthrough(), - attachmentType: attachmentDownloadTypeSchema, + attachmentType: messageAttachmentTypeSchema, ciphertextSize: z.number(), contentType: MIMETypeSchema, digest: z.string(), diff --git a/ts/sql/server/messageAttachments.ts b/ts/sql/server/messageAttachments.ts index 7009edf506d..bf4dd226a43 100644 --- a/ts/sql/server/messageAttachments.ts +++ b/ts/sql/server/messageAttachments.ts @@ -3,7 +3,7 @@ import { z } from 'zod'; import { convertUndefinedToNull } from '../../util/dropNull.js'; -import { attachmentDownloadTypeSchema } from '../../types/AttachmentDownload.js'; +import { messageAttachmentTypeSchema } from '../../types/AttachmentDownload.js'; import { APPLICATION_OCTET_STREAM } from '../../types/MIME.js'; import type { MessageAttachmentDBType } from '../Interface.js'; @@ -35,7 +35,7 @@ export const permissiveMessageAttachmentSchema = z.object({ messageId: z.string(), messageType: z.string(), editHistoryIndex: z.number(), - attachmentType: attachmentDownloadTypeSchema, + attachmentType: messageAttachmentTypeSchema, orderInMessage: z.number(), conversationId: z.string(), sentAt: z.number().catch(0), diff --git a/ts/state/selectors/message.ts b/ts/state/selectors/message.ts index 2067d6e77ba..edc337ab621 100644 --- a/ts/state/selectors/message.ts +++ b/ts/state/selectors/message.ts @@ -71,7 +71,7 @@ import { isIncremental, defaultBlurHash, } from '../../types/Attachment.js'; -import type { AttachmentDownloadJobTypeType } from '../../types/AttachmentDownload.js'; +import type { MessageAttachmentType } from '../../types/AttachmentDownload.js'; import { type DefaultConversationColorType } from '../../types/Colors.js'; import { ReadStatus } from '../../messages/MessageReadStatus.js'; @@ -1852,7 +1852,7 @@ export function getPropsForEmbeddedContact( export function getPropsForAttachment( attachment: AttachmentType, - disposition: AttachmentDownloadJobTypeType, + disposition: MessageAttachmentType, message: Pick ): AttachmentForUIType { const { path, pending, screenshot, thumbnail, thumbnailFromBackup } = diff --git a/ts/types/Attachment.ts b/ts/types/Attachment.ts index 780319f730f..db34d7a7a78 100644 --- a/ts/types/Attachment.ts +++ b/ts/types/Attachment.ts @@ -37,6 +37,7 @@ import { } from './Crypto.js'; import { missingCaseError } from '../util/missingCaseError.js'; import type { MakeVideoScreenshotResultType } from './VisualAttachment.js'; +import type { MessageAttachmentType } from './AttachmentDownload.js'; const { isNumber, @@ -482,6 +483,7 @@ const THUMBNAIL_CONTENT_TYPE = MIME.IMAGE_PNG; export async function captureDimensionsAndScreenshot( attachment: AttachmentType, + options: { generateThumbnail: boolean }, params: { writeNewAttachmentData: ( data: Uint8Array @@ -544,28 +546,35 @@ export async function captureDimensionsAndScreenshot( objectUrl: localUrl, logger, }); - const thumbnailBuffer = await blobToArrayBuffer( - await makeImageThumbnail({ - size: THUMBNAIL_SIZE, - objectUrl: localUrl, - contentType: THUMBNAIL_CONTENT_TYPE, - logger, - }) - ); + let thumbnail: LocalAttachmentV2Type | undefined; + + if (options.generateThumbnail) { + const thumbnailBuffer = await blobToArrayBuffer( + await makeImageThumbnail({ + size: THUMBNAIL_SIZE, + objectUrl: localUrl, + contentType: THUMBNAIL_CONTENT_TYPE, + logger, + }) + ); + + thumbnail = await writeNewAttachmentData( + new Uint8Array(thumbnailBuffer) + ); + } - const thumbnail = await writeNewAttachmentData( - new Uint8Array(thumbnailBuffer) - ); return { ...attachment, width, height, - thumbnail: { - ...thumbnail, - contentType: THUMBNAIL_CONTENT_TYPE, - width: THUMBNAIL_SIZE, - height: THUMBNAIL_SIZE, - }, + thumbnail: thumbnail + ? { + ...thumbnail, + contentType: THUMBNAIL_CONTENT_TYPE, + width: THUMBNAIL_SIZE, + height: THUMBNAIL_SIZE, + } + : undefined, }; } catch (error) { logger.error( @@ -597,18 +606,19 @@ export async function captureDimensionsAndScreenshot( new Uint8Array(screenshotBuffer) ); - const thumbnailBuffer = await blobToArrayBuffer( - await makeImageThumbnail({ - size: THUMBNAIL_SIZE, - objectUrl: screenshotObjectUrl, - contentType: THUMBNAIL_CONTENT_TYPE, - logger, - }) - ); + let thumbnail: LocalAttachmentV2Type | undefined; + if (options.generateThumbnail) { + const thumbnailBuffer = await blobToArrayBuffer( + await makeImageThumbnail({ + size: THUMBNAIL_SIZE, + objectUrl: screenshotObjectUrl, + contentType: THUMBNAIL_CONTENT_TYPE, + logger, + }) + ); - const thumbnail = await writeNewAttachmentData( - new Uint8Array(thumbnailBuffer) - ); + thumbnail = await writeNewAttachmentData(new Uint8Array(thumbnailBuffer)); + } return { ...attachment, @@ -619,12 +629,14 @@ export async function captureDimensionsAndScreenshot( width, height, }, - thumbnail: { - ...thumbnail, - contentType: THUMBNAIL_CONTENT_TYPE, - width: THUMBNAIL_SIZE, - height: THUMBNAIL_SIZE, - }, + thumbnail: thumbnail + ? { + ...thumbnail, + contentType: THUMBNAIL_CONTENT_TYPE, + width: THUMBNAIL_SIZE, + height: THUMBNAIL_SIZE, + } + : undefined, width, height, }; @@ -1421,3 +1433,12 @@ export function partitionBodyAndNormalAttachments< attachments: normalAttachments, }; } + +const MESSAGE_ATTACHMENT_TYPES_NEEDING_THUMBNAILS: Set = + new Set(['attachment', 'sticker']); + +export function shouldGenerateThumbnailForAttachmentType( + type: MessageAttachmentType +): boolean { + return MESSAGE_ATTACHMENT_TYPES_NEEDING_THUMBNAILS.has(type); +} diff --git a/ts/types/AttachmentDownload.ts b/ts/types/AttachmentDownload.ts index 8d34104868b..52f0ec07f99 100644 --- a/ts/types/AttachmentDownload.ts +++ b/ts/types/AttachmentDownload.ts @@ -14,7 +14,7 @@ export enum MediaTier { BACKUP = 'backup', } -export const attachmentDownloadTypeSchema = z.enum([ +export const messageAttachmentTypeSchema = z.enum([ 'long-message', 'attachment', 'preview', @@ -23,13 +23,11 @@ export const attachmentDownloadTypeSchema = z.enum([ 'sticker', ]); -export type AttachmentDownloadJobTypeType = z.infer< - typeof attachmentDownloadTypeSchema ->; +export type MessageAttachmentType = z.infer; export type CoreAttachmentDownloadJobType = { attachment: AttachmentType; - attachmentType: AttachmentDownloadJobTypeType; + attachmentType: MessageAttachmentType; ciphertextSize: number; contentType: MIMEType; attachmentSignature: string; @@ -49,7 +47,7 @@ export const coreAttachmentDownloadJobSchema = z.object({ attachment: z .object({ size: z.number(), contentType: MIMETypeSchema }) .passthrough(), - attachmentType: attachmentDownloadTypeSchema, + attachmentType: messageAttachmentTypeSchema, ciphertextSize: z.number(), contentType: MIMETypeSchema, attachmentSignature: z.string(), diff --git a/ts/types/Message2.ts b/ts/types/Message2.ts index fed9b53f92b..37da97a5d11 100644 --- a/ts/types/Message2.ts +++ b/ts/types/Message2.ts @@ -16,6 +16,7 @@ import { removeSchemaVersion, replaceUnicodeOrderOverrides, replaceUnicodeV2, + shouldGenerateThumbnailForAttachmentType, } from './Attachment.js'; import type { MakeVideoScreenshotResultType } from './VisualAttachment.js'; import * as Errors from './errors.js'; @@ -48,6 +49,7 @@ import { encryptLegacyAttachment } from '../util/encryptLegacyAttachment.js'; import { deepClone } from '../util/deepClone.js'; import * as Bytes from '../Bytes.js'; import { isBodyTooLong } from '../util/longAttachment.js'; +import type { MessageAttachmentType } from './AttachmentDownload.js'; const { isFunction, isObject, identity } = lodash; @@ -493,7 +495,13 @@ const toVersion7 = _withSchemaVersion({ const toVersion8 = _withSchemaVersion({ schemaVersion: 8, - upgrade: _mapAttachments(captureDimensionsAndScreenshot), + upgrade: _mapAttachments((attachment, context) => + captureDimensionsAndScreenshot( + attachment, + { generateThumbnail: true }, + context + ) + ), }); const toVersion9 = _withSchemaVersion({ @@ -768,6 +776,7 @@ export const upgradeSchema = async ( // downloaded out of band. export const processNewAttachment = async ( attachment: AttachmentType, + attachmentType: MessageAttachmentType, { writeNewAttachmentData, makeObjectUrl, @@ -810,15 +819,22 @@ export const processNewAttachment = async ( throw new TypeError('context.logger is required'); } - const finalAttachment = await captureDimensionsAndScreenshot(attachment, { - writeNewAttachmentData, - makeObjectUrl, - revokeObjectUrl, - getImageDimensions, - makeImageThumbnail, - makeVideoScreenshot, - logger, - }); + const finalAttachment = await captureDimensionsAndScreenshot( + attachment, + { + generateThumbnail: + shouldGenerateThumbnailForAttachmentType(attachmentType), + }, + { + writeNewAttachmentData, + makeObjectUrl, + revokeObjectUrl, + getImageDimensions, + makeImageThumbnail, + makeVideoScreenshot, + logger, + } + ); return finalAttachment; }; diff --git a/ts/util/downloadOnboardingStory.ts b/ts/util/downloadOnboardingStory.ts index abcc0078146..66613f73d30 100644 --- a/ts/util/downloadOnboardingStory.ts +++ b/ts/util/downloadOnboardingStory.ts @@ -73,7 +73,10 @@ export async function downloadOnboardingStory(): Promise { ...local, }; - return window.Signal.Migrations.processNewAttachment(attachment); + return window.Signal.Migrations.processNewAttachment( + attachment, + 'attachment' + ); }) );