2021-07-15 04:51:20 +02:00
|
|
|
/*
|
|
|
|
Copyright 2021 The Matrix.org Foundation C.I.C.
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
2022-03-01 21:42:05 +01:00
|
|
|
import { MatrixEvent } from "matrix-js-sdk/src/matrix";
|
2021-10-23 00:23:32 +02:00
|
|
|
import { EventType, MsgType } from "matrix-js-sdk/src/@types/event";
|
|
|
|
import { logger } from "matrix-js-sdk/src/logger";
|
|
|
|
|
2021-07-15 04:51:20 +02:00
|
|
|
import { LazyValue } from "./LazyValue";
|
|
|
|
import { Media, mediaFromContent } from "../customisations/Media";
|
|
|
|
import { decryptFile } from "./DecryptFile";
|
|
|
|
import { IMediaEventContent } from "../customisations/models/IMediaEventContent";
|
|
|
|
import { IDestroyable } from "./IDestroyable";
|
2021-10-15 16:31:29 +02:00
|
|
|
|
2021-07-15 04:51:20 +02:00
|
|
|
// TODO: We should consider caching the blobs. https://github.com/vector-im/element-web/issues/17192
|
|
|
|
|
|
|
|
export class MediaEventHelper implements IDestroyable {
|
2021-07-20 16:55:07 +02:00
|
|
|
// Either an HTTP or Object URL (when encrypted) to the media.
|
2021-07-15 04:51:20 +02:00
|
|
|
public readonly sourceUrl: LazyValue<string>;
|
|
|
|
public readonly thumbnailUrl: LazyValue<string>;
|
2021-07-20 16:55:07 +02:00
|
|
|
|
|
|
|
// Either the raw or decrypted (when encrypted) contents of the file.
|
2021-07-15 04:51:20 +02:00
|
|
|
public readonly sourceBlob: LazyValue<Blob>;
|
|
|
|
public readonly thumbnailBlob: LazyValue<Blob>;
|
2021-07-20 16:55:07 +02:00
|
|
|
|
2021-07-15 04:51:20 +02:00
|
|
|
public readonly media: Media;
|
|
|
|
|
|
|
|
public constructor(private event: MatrixEvent) {
|
|
|
|
this.sourceUrl = new LazyValue(this.prepareSourceUrl);
|
|
|
|
this.thumbnailUrl = new LazyValue(this.prepareThumbnailUrl);
|
|
|
|
this.sourceBlob = new LazyValue(this.fetchSource);
|
|
|
|
this.thumbnailBlob = new LazyValue(this.fetchThumbnail);
|
|
|
|
|
|
|
|
this.media = mediaFromContent(this.event.getContent());
|
|
|
|
}
|
|
|
|
|
2021-07-16 23:55:07 +02:00
|
|
|
public get fileName(): string {
|
2022-06-30 17:22:52 +02:00
|
|
|
return this.event.getContent<IMediaEventContent>().filename
|
|
|
|
|| this.event.getContent<IMediaEventContent>().body
|
|
|
|
|| "download";
|
2021-07-16 23:55:07 +02:00
|
|
|
}
|
|
|
|
|
2021-07-15 04:51:20 +02:00
|
|
|
public destroy() {
|
|
|
|
if (this.media.isEncrypted) {
|
|
|
|
if (this.sourceUrl.present) URL.revokeObjectURL(this.sourceUrl.cachedValue);
|
|
|
|
if (this.thumbnailUrl.present) URL.revokeObjectURL(this.thumbnailUrl.cachedValue);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private prepareSourceUrl = async () => {
|
|
|
|
if (this.media.isEncrypted) {
|
|
|
|
const blob = await this.sourceBlob.value;
|
|
|
|
return URL.createObjectURL(blob);
|
|
|
|
} else {
|
|
|
|
return this.media.srcHttp;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
private prepareThumbnailUrl = async () => {
|
|
|
|
if (this.media.isEncrypted) {
|
|
|
|
const blob = await this.thumbnailBlob.value;
|
2021-07-22 23:49:30 +02:00
|
|
|
if (blob === null) return null;
|
2021-07-15 04:51:20 +02:00
|
|
|
return URL.createObjectURL(blob);
|
|
|
|
} else {
|
|
|
|
return this.media.thumbnailHttp;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
private fetchSource = () => {
|
|
|
|
if (this.media.isEncrypted) {
|
2021-08-11 01:25:56 +02:00
|
|
|
const content = this.event.getContent<IMediaEventContent>();
|
|
|
|
return decryptFile(content.file, content.info);
|
2021-07-15 04:51:20 +02:00
|
|
|
}
|
|
|
|
return this.media.downloadSource().then(r => r.blob());
|
|
|
|
};
|
|
|
|
|
|
|
|
private fetchThumbnail = () => {
|
|
|
|
if (!this.media.hasThumbnail) return Promise.resolve(null);
|
|
|
|
|
|
|
|
if (this.media.isEncrypted) {
|
|
|
|
const content = this.event.getContent<IMediaEventContent>();
|
|
|
|
if (content.info?.thumbnail_file) {
|
2021-08-11 01:25:56 +02:00
|
|
|
return decryptFile(content.info.thumbnail_file, content.info.thumbnail_info);
|
2021-07-15 04:51:20 +02:00
|
|
|
} else {
|
|
|
|
// "Should never happen"
|
2021-10-15 16:31:29 +02:00
|
|
|
logger.warn("Media claims to have thumbnail and is encrypted, but no thumbnail_file found");
|
2021-07-15 04:51:20 +02:00
|
|
|
return Promise.resolve(null);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return fetch(this.media.thumbnailHttp).then(r => r.blob());
|
|
|
|
};
|
2021-07-15 22:19:07 +02:00
|
|
|
|
|
|
|
public static isEligible(event: MatrixEvent): boolean {
|
|
|
|
if (!event) return false;
|
|
|
|
if (event.isRedacted()) return false;
|
|
|
|
if (event.getType() === EventType.Sticker) return true;
|
|
|
|
if (event.getType() !== EventType.RoomMessage) return false;
|
|
|
|
|
|
|
|
const content = event.getContent();
|
|
|
|
const mediaMsgTypes: string[] = [
|
|
|
|
MsgType.Video,
|
|
|
|
MsgType.Audio,
|
|
|
|
MsgType.Image,
|
|
|
|
MsgType.File,
|
|
|
|
];
|
|
|
|
if (mediaMsgTypes.includes(content.msgtype)) return true;
|
|
|
|
if (typeof(content.url) === 'string') return true;
|
|
|
|
|
|
|
|
// Finally, it's probably not media
|
|
|
|
return false;
|
|
|
|
}
|
2021-07-15 04:51:20 +02:00
|
|
|
}
|