diff --git a/src/ContentMessages.ts b/src/ContentMessages.ts index 7cb0ad1db9..f7b12f53e3 100644 --- a/src/ContentMessages.ts +++ b/src/ContentMessages.ts @@ -23,7 +23,7 @@ import encrypt from "matrix-encrypt-attachment"; import extractPngChunks from "png-chunks-extract"; import { IAbortablePromise, IImageInfo } from "matrix-js-sdk/src/@types/partials"; import { logger } from "matrix-js-sdk/src/logger"; -import { IEventRelation, ISendEventResponse, MatrixEvent } from "matrix-js-sdk/src/matrix"; +import { IEventRelation, ISendEventResponse, MatrixError, MatrixEvent } from "matrix-js-sdk/src/matrix"; import { THREAD_RELATION_TYPE } from "matrix-js-sdk/src/models/thread"; import { IEncryptedFile, IMediaEventInfo } from "./customisations/models/IMediaEventContent"; @@ -64,10 +64,7 @@ interface IMediaConfig { interface IContent { body: string; msgtype: string; - info: { - size: number; - mimetype?: string; - }; + info: IMediaEventInfo; file?: string; url?: string; } @@ -129,6 +126,9 @@ const IMAGE_THUMBNAIL_MIN_REDUCTION_PERCENT = 0.1; // 10% // We don't apply these thresholds to video thumbnails as a poster image is always useful // and videos tend to be much larger. +// Image mime types for which to always include a thumbnail for even if it is larger than the input for wider support. +const ALWAYS_INCLUDE_THUMBNAIL = ["image/avif", "image/webp"]; + /** * Read the metadata for an image file and create and upload a thumbnail of the image. * @@ -137,7 +137,11 @@ const IMAGE_THUMBNAIL_MIN_REDUCTION_PERCENT = 0.1; // 10% * @param {File} imageFile The image to read and thumbnail. * @return {Promise} A promise that resolves with the attachment info. */ -async function infoForImageFile(matrixClient: MatrixClient, roomId: string, imageFile: File) { +async function infoForImageFile( + matrixClient: MatrixClient, + roomId: string, + imageFile: File, +): Promise> { let thumbnailType = "image/png"; if (imageFile.type === "image/jpeg") { thumbnailType = "image/jpeg"; @@ -149,7 +153,7 @@ async function infoForImageFile(matrixClient: MatrixClient, roomId: string, imag const imageInfo = result.info; // For lesser supported image types, always include the thumbnail even if it is larger - if (!["image/avif", "image/webp"].includes(imageFile.type)) { + if (!ALWAYS_INCLUDE_THUMBNAIL.includes(imageFile.type)) { // we do all sizing checks here because we still rely on thumbnail generation for making a blurhash from. const sizeDifference = imageFile.size - imageInfo.thumbnail_info.size; if ( @@ -178,7 +182,7 @@ async function infoForImageFile(matrixClient: MatrixClient, roomId: string, imag * @param {File} videoFile The file to load in an video element. * @return {Promise} A promise that resolves with the video image element. */ -function loadVideoElement(videoFile): Promise { +function loadVideoElement(videoFile: File): Promise { return new Promise((resolve, reject) => { // Load the file into an html element const video = document.createElement("video"); @@ -224,7 +228,11 @@ function loadVideoElement(videoFile): Promise { * @param {File} videoFile The video to read and thumbnail. * @return {Promise} A promise that resolves with the attachment info. */ -function infoForVideoFile(matrixClient, roomId, videoFile) { +function infoForVideoFile( + matrixClient: MatrixClient, + roomId: string, + videoFile: File, +): Promise> { const thumbnailType = "image/jpeg"; let videoInfo: Partial; @@ -449,7 +457,7 @@ export default class ContentMessages { }); } - public cancelUpload(promise: Promise, matrixClient: MatrixClient): void { + public cancelUpload(promise: IAbortablePromise, matrixClient: MatrixClient): void { const upload = this.inprogress.find(item => item.promise === promise); if (upload) { upload.canceled = true; @@ -466,12 +474,12 @@ export default class ContentMessages { replyToEvent: MatrixEvent | undefined, promBefore: Promise, ) { - const content: IContent = { + const content: Omit & { info: Partial } = { body: file.name || 'Attachment', info: { size: file.size, }, - msgtype: "", // set later + msgtype: MsgType.File, // set more specifically later }; attachRelation(content, relation); @@ -497,6 +505,7 @@ export default class ContentMessages { Object.assign(content.info, imageInfo); resolve(); }, (e) => { + // Failed to thumbnail, fall back to uploading an m.file logger.error(e); content.msgtype = MsgType.File; resolve(); @@ -510,6 +519,8 @@ export default class ContentMessages { Object.assign(content.info, videoInfo); resolve(); }, (e) => { + // Failed to thumbnail, fall back to uploading an m.file + logger.error(e); content.msgtype = MsgType.File; resolve(); }); @@ -541,8 +552,8 @@ export default class ContentMessages { dis.dispatch({ action: Action.UploadProgress, upload }); } - let error; - return prom.then(function() { + let error: MatrixError; + return prom.then(() => { if (upload.canceled) throw new UploadCanceledError(); // XXX: upload.promise must be the promise that // is returned by uploadFile as it has an abort() @@ -567,11 +578,11 @@ export default class ContentMessages { }); } return prom; - }, function(err) { + }, function(err: MatrixError) { error = err; if (!upload.canceled) { let desc = _t("The file '%(fileName)s' failed to upload.", { fileName: upload.fileName }); - if (err.http_status === 413) { + if (err.httpStatus === 413) { desc = _t( "The file '%(fileName)s' exceeds this homeserver's size limit for uploads", { fileName: upload.fileName }, @@ -593,7 +604,7 @@ export default class ContentMessages { // 413: File was too big or upset the server in some way: // clear the media size limit so we fetch it again next time // we try to upload - if (error && error.http_status === 413) { + if (error?.httpStatus === 413) { this.mediaConfig = null; } dis.dispatch({ action: Action.UploadFailed, upload, error }); @@ -613,7 +624,7 @@ export default class ContentMessages { return true; } - private ensureMediaConfigFetched(matrixClient: MatrixClient) { + private ensureMediaConfigFetched(matrixClient: MatrixClient): Promise { if (this.mediaConfig !== null) return; logger.log("[Media Config] Fetching");