mv element.io @types __mocks__/ debian docker module_system/ playwright res src test webapp Dockerfile .dockerignore .eslintignore .stylelintrc.cjs babel.config.cjs recorder-worklet-loader.cjs .modernizr.json components.json config.json config.sample.json package.json project.json tsconfig.json tsconfig.module_system.json jest.config.ts playwright.config.ts webpack.config.ts build_config.sample.yaml apps/web/
mkdir apps/web/scripts
mv scripts/{cleanup.sh,ci_package.sh,copy-res.ts,deploy.py,package.sh} apps/web/scripts
And a couple of gitignore tweaks
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { type MatrixEvent, EventType, MsgType } from "matrix-js-sdk/src/matrix";
|
||||
import { type FileContent, type ImageContent, type MediaEventContent } from "matrix-js-sdk/src/types";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import { LazyValue } from "./LazyValue";
|
||||
import { type Media, mediaFromContent } from "../customisations/Media";
|
||||
import { decryptFile } from "./DecryptFile";
|
||||
import { type IDestroyable } from "./IDestroyable";
|
||||
import { getBlobSafeMimeType } from "./blobs.ts";
|
||||
|
||||
// TODO: We should consider caching the blobs. https://github.com/vector-im/element-web/issues/17192
|
||||
|
||||
export class MediaEventHelper implements IDestroyable {
|
||||
// Either an HTTP or Object URL (when encrypted) to the media.
|
||||
public readonly sourceUrl: LazyValue<string | null>;
|
||||
public readonly thumbnailUrl: LazyValue<string | null>;
|
||||
|
||||
// Either the raw or decrypted (when encrypted) contents of the file.
|
||||
public readonly sourceBlob: LazyValue<Blob>;
|
||||
public readonly thumbnailBlob: LazyValue<Blob | null>;
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
public get fileName(): string {
|
||||
return (
|
||||
this.event.getContent<FileContent>().filename ||
|
||||
this.event.getContent<MediaEventContent>().body ||
|
||||
"download"
|
||||
);
|
||||
}
|
||||
|
||||
public destroy(): void {
|
||||
if (this.media.isEncrypted) {
|
||||
if (this.sourceUrl.cachedValue) URL.revokeObjectURL(this.sourceUrl.cachedValue);
|
||||
if (this.thumbnailUrl.cachedValue) URL.revokeObjectURL(this.thumbnailUrl.cachedValue);
|
||||
}
|
||||
}
|
||||
|
||||
private prepareSourceUrl = async (): Promise<string | null> => {
|
||||
if (this.media.isEncrypted) {
|
||||
const blob = await this.sourceBlob.value;
|
||||
return URL.createObjectURL(blob);
|
||||
} else {
|
||||
return this.media.srcHttp;
|
||||
}
|
||||
};
|
||||
|
||||
private prepareThumbnailUrl = async (): Promise<string | null> => {
|
||||
if (this.media.isEncrypted) {
|
||||
const blob = await this.thumbnailBlob.value;
|
||||
if (blob === null) return null;
|
||||
return URL.createObjectURL(blob);
|
||||
} else {
|
||||
return this.media.thumbnailHttp;
|
||||
}
|
||||
};
|
||||
|
||||
private fetchSource = (): Promise<Blob> => {
|
||||
const content = this.event.getContent<MediaEventContent>();
|
||||
if (this.media.isEncrypted) {
|
||||
return decryptFile(content.file!, content.info);
|
||||
}
|
||||
|
||||
return (
|
||||
this.media
|
||||
.downloadSource()
|
||||
.then((r) => r.blob())
|
||||
// Set the mime type from the event info on the blob
|
||||
.then((blob) => blob.slice(0, blob.size, getBlobSafeMimeType(content.info?.mimetype ?? blob.type)))
|
||||
);
|
||||
};
|
||||
|
||||
private fetchThumbnail = (): Promise<Blob | null> => {
|
||||
if (!this.media.hasThumbnail) return Promise.resolve(null);
|
||||
|
||||
const content = this.event.getContent<ImageContent>();
|
||||
if (this.media.isEncrypted) {
|
||||
if (content.info?.thumbnail_file) {
|
||||
return decryptFile(content.info.thumbnail_file, content.info.thumbnail_info);
|
||||
} else {
|
||||
// "Should never happen"
|
||||
logger.warn("Media claims to have thumbnail and is encrypted, but no thumbnail_file found");
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
}
|
||||
|
||||
const thumbnailHttp = this.media.thumbnailHttp;
|
||||
if (!thumbnailHttp) return Promise.resolve(null);
|
||||
|
||||
return (
|
||||
fetch(thumbnailHttp)
|
||||
.then((r) => r.blob())
|
||||
// Set the mime type from the event info on the blob
|
||||
.then((blob) =>
|
||||
blob.slice(0, blob.size, getBlobSafeMimeType(content.info?.thumbnail_info?.mimetype ?? blob.type)),
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the media event in question supports being hidden in the timeline.
|
||||
* @param event Any matrix event.
|
||||
* @returns `true` if the media can be hidden, otherwise `false`.
|
||||
*/
|
||||
public static canHide(event: MatrixEvent): boolean {
|
||||
if (!event) return false;
|
||||
if (event.isRedacted()) return false;
|
||||
const content = event.getContent();
|
||||
const hideTypes: string[] = [MsgType.Video, MsgType.Image];
|
||||
if (hideTypes.includes(content.msgtype!)) return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user