mirror of
https://github.com/element-hq/element-web
synced 2024-11-28 20:38:55 +03:00
Reuse media content/info types from the js-sdk (#12308)
This commit is contained in:
parent
89eb884821
commit
431ae32304
19 changed files with 74 additions and 258 deletions
|
@ -76,6 +76,7 @@ module.exports = {
|
||||||
group: [
|
group: [
|
||||||
"matrix-js-sdk/src/**",
|
"matrix-js-sdk/src/**",
|
||||||
"!matrix-js-sdk/src/matrix",
|
"!matrix-js-sdk/src/matrix",
|
||||||
|
"!matrix-js-sdk/src/types",
|
||||||
"matrix-js-sdk/lib",
|
"matrix-js-sdk/lib",
|
||||||
"matrix-js-sdk/lib/",
|
"matrix-js-sdk/lib/",
|
||||||
"matrix-js-sdk/lib/**",
|
"matrix-js-sdk/lib/**",
|
||||||
|
|
27
src/@types/matrix-js-sdk.d.ts
vendored
Normal file
27
src/@types/matrix-js-sdk.d.ts
vendored
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
Copyright 2024 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { BLURHASH_FIELD } from "../utils/image-media";
|
||||||
|
|
||||||
|
// Matrix JS SDK extensions
|
||||||
|
declare module "matrix-js-sdk" {
|
||||||
|
export interface FileInfo {
|
||||||
|
/**
|
||||||
|
* @see https://github.com/matrix-org/matrix-spec-proposals/pull/2448
|
||||||
|
*/
|
||||||
|
[BLURHASH_FIELD]?: string;
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,19 +28,19 @@ import {
|
||||||
UploadProgress,
|
UploadProgress,
|
||||||
THREAD_RELATION_TYPE,
|
THREAD_RELATION_TYPE,
|
||||||
} from "matrix-js-sdk/src/matrix";
|
} from "matrix-js-sdk/src/matrix";
|
||||||
|
import {
|
||||||
|
ImageInfo,
|
||||||
|
AudioInfo,
|
||||||
|
VideoInfo,
|
||||||
|
EncryptedFile,
|
||||||
|
MediaEventContent,
|
||||||
|
MediaEventInfo,
|
||||||
|
} from "matrix-js-sdk/src/types";
|
||||||
import encrypt from "matrix-encrypt-attachment";
|
import encrypt from "matrix-encrypt-attachment";
|
||||||
import extractPngChunks from "png-chunks-extract";
|
import extractPngChunks from "png-chunks-extract";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
import { removeElement } from "matrix-js-sdk/src/utils";
|
import { removeElement } from "matrix-js-sdk/src/utils";
|
||||||
|
|
||||||
import {
|
|
||||||
AudioInfo,
|
|
||||||
EncryptedFile,
|
|
||||||
ImageInfo,
|
|
||||||
IMediaEventContent,
|
|
||||||
IMediaEventInfo,
|
|
||||||
VideoInfo,
|
|
||||||
} from "./customisations/models/IMediaEventContent";
|
|
||||||
import dis from "./dispatcher/dispatcher";
|
import dis from "./dispatcher/dispatcher";
|
||||||
import { _t } from "./languageHandler";
|
import { _t } from "./languageHandler";
|
||||||
import Modal from "./Modal";
|
import Modal from "./Modal";
|
||||||
|
@ -537,7 +537,7 @@ export default class ContentMessages {
|
||||||
promBefore?: Promise<any>,
|
promBefore?: Promise<any>,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const fileName = file.name || _t("common|attachment");
|
const fileName = file.name || _t("common|attachment");
|
||||||
const content: Omit<IMediaEventContent, "info"> & { info: Partial<IMediaEventInfo> } = {
|
const content: Omit<MediaEventContent, "info"> & { info: Partial<MediaEventInfo> } = {
|
||||||
body: fileName,
|
body: fileName,
|
||||||
info: {
|
info: {
|
||||||
size: file.size,
|
size: file.size,
|
||||||
|
|
|
@ -17,12 +17,12 @@ limitations under the License.
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
import { IContent } from "matrix-js-sdk/src/matrix";
|
import { IContent } from "matrix-js-sdk/src/matrix";
|
||||||
|
import { MediaEventContent } from "matrix-js-sdk/src/types";
|
||||||
|
|
||||||
import { Playback } from "../../../audio/Playback";
|
import { Playback } from "../../../audio/Playback";
|
||||||
import InlineSpinner from "../elements/InlineSpinner";
|
import InlineSpinner from "../elements/InlineSpinner";
|
||||||
import { _t } from "../../../languageHandler";
|
import { _t } from "../../../languageHandler";
|
||||||
import AudioPlayer from "../audio_messages/AudioPlayer";
|
import AudioPlayer from "../audio_messages/AudioPlayer";
|
||||||
import { IMediaEventContent } from "../../../customisations/models/IMediaEventContent";
|
|
||||||
import MFileBody from "./MFileBody";
|
import MFileBody from "./MFileBody";
|
||||||
import { IBodyProps } from "./IBodyProps";
|
import { IBodyProps } from "./IBodyProps";
|
||||||
import { PlaybackManager } from "../../../audio/PlaybackManager";
|
import { PlaybackManager } from "../../../audio/PlaybackManager";
|
||||||
|
@ -67,7 +67,7 @@ export default class MAudioBody extends React.PureComponent<IBodyProps, IState>
|
||||||
// We should have a buffer to work with now: let's set it up
|
// We should have a buffer to work with now: let's set it up
|
||||||
|
|
||||||
// Note: we don't actually need a waveform to render an audio event, but voice messages do.
|
// Note: we don't actually need a waveform to render an audio event, but voice messages do.
|
||||||
const content = this.props.mxEvent.getContent<IMediaEventContent & IContent>();
|
const content = this.props.mxEvent.getContent<MediaEventContent & IContent>();
|
||||||
const waveform = content?.["org.matrix.msc1767.audio"]?.waveform?.map((p: number) => p / 1024);
|
const waveform = content?.["org.matrix.msc1767.audio"]?.waveform?.map((p: number) => p / 1024);
|
||||||
|
|
||||||
// We should have a buffer to work with now: let's set it up
|
// We should have a buffer to work with now: let's set it up
|
||||||
|
|
|
@ -16,6 +16,7 @@ limitations under the License.
|
||||||
|
|
||||||
import React, { AllHTMLAttributes, createRef } from "react";
|
import React, { AllHTMLAttributes, createRef } from "react";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
import { MediaEventContent } from "matrix-js-sdk/src/types";
|
||||||
|
|
||||||
import { _t } from "../../../languageHandler";
|
import { _t } from "../../../languageHandler";
|
||||||
import Modal from "../../../Modal";
|
import Modal from "../../../Modal";
|
||||||
|
@ -23,7 +24,6 @@ import AccessibleButton from "../elements/AccessibleButton";
|
||||||
import { mediaFromContent } from "../../../customisations/Media";
|
import { mediaFromContent } from "../../../customisations/Media";
|
||||||
import ErrorDialog from "../dialogs/ErrorDialog";
|
import ErrorDialog from "../dialogs/ErrorDialog";
|
||||||
import { fileSize, presentableTextForFile } from "../../../utils/FileUtils";
|
import { fileSize, presentableTextForFile } from "../../../utils/FileUtils";
|
||||||
import { IMediaEventContent } from "../../../customisations/models/IMediaEventContent";
|
|
||||||
import { IBodyProps } from "./IBodyProps";
|
import { IBodyProps } from "./IBodyProps";
|
||||||
import { FileDownloader } from "../../../utils/FileDownloader";
|
import { FileDownloader } from "../../../utils/FileDownloader";
|
||||||
import TextWithTooltip from "../elements/TextWithTooltip";
|
import TextWithTooltip from "../elements/TextWithTooltip";
|
||||||
|
@ -128,8 +128,8 @@ export default class MFileBody extends React.Component<IProps, IState> {
|
||||||
const media = mediaFromContent(this.props.mxEvent.getContent());
|
const media = mediaFromContent(this.props.mxEvent.getContent());
|
||||||
return media.srcHttp;
|
return media.srcHttp;
|
||||||
}
|
}
|
||||||
private get content(): IMediaEventContent {
|
private get content(): MediaEventContent {
|
||||||
return this.props.mxEvent.getContent<IMediaEventContent>();
|
return this.props.mxEvent.getContent<MediaEventContent>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private get fileName(): string {
|
private get fileName(): string {
|
||||||
|
|
|
@ -21,6 +21,7 @@ import classNames from "classnames";
|
||||||
import { CSSTransition, SwitchTransition } from "react-transition-group";
|
import { CSSTransition, SwitchTransition } from "react-transition-group";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
import { ClientEvent, ClientEventHandlerMap } from "matrix-js-sdk/src/matrix";
|
import { ClientEvent, ClientEventHandlerMap } from "matrix-js-sdk/src/matrix";
|
||||||
|
import { ImageContent } from "matrix-js-sdk/src/types";
|
||||||
import { Tooltip } from "@vector-im/compound-web";
|
import { Tooltip } from "@vector-im/compound-web";
|
||||||
|
|
||||||
import MFileBody from "./MFileBody";
|
import MFileBody from "./MFileBody";
|
||||||
|
@ -30,7 +31,6 @@ import SettingsStore from "../../../settings/SettingsStore";
|
||||||
import Spinner from "../elements/Spinner";
|
import Spinner from "../elements/Spinner";
|
||||||
import { Media, mediaFromContent } from "../../../customisations/Media";
|
import { Media, mediaFromContent } from "../../../customisations/Media";
|
||||||
import { BLURHASH_FIELD, createThumbnail } from "../../../utils/image-media";
|
import { BLURHASH_FIELD, createThumbnail } from "../../../utils/image-media";
|
||||||
import { ImageContent } from "../../../customisations/models/IMediaEventContent";
|
|
||||||
import ImageView from "../elements/ImageView";
|
import ImageView from "../elements/ImageView";
|
||||||
import { IBodyProps } from "./IBodyProps";
|
import { IBodyProps } from "./IBodyProps";
|
||||||
import { ImageSize, suggestedSize as suggestedImageSize } from "../../../settings/enums/ImageSize";
|
import { ImageSize, suggestedSize as suggestedImageSize } from "../../../settings/enums/ImageSize";
|
||||||
|
|
|
@ -15,9 +15,9 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { ImageContent } from "matrix-js-sdk/src/types";
|
||||||
|
|
||||||
import MImageBody from "./MImageBody";
|
import MImageBody from "./MImageBody";
|
||||||
import { ImageContent } from "../../../customisations/models/IMediaEventContent";
|
|
||||||
|
|
||||||
const FORCED_IMAGE_HEIGHT = 44;
|
const FORCED_IMAGE_HEIGHT = 44;
|
||||||
|
|
||||||
|
|
|
@ -16,10 +16,10 @@ limitations under the License.
|
||||||
|
|
||||||
import React, { ComponentProps, ReactNode } from "react";
|
import React, { ComponentProps, ReactNode } from "react";
|
||||||
import { Tooltip } from "@vector-im/compound-web";
|
import { Tooltip } from "@vector-im/compound-web";
|
||||||
|
import { MediaEventContent } from "matrix-js-sdk/src/types";
|
||||||
|
|
||||||
import MImageBody from "./MImageBody";
|
import MImageBody from "./MImageBody";
|
||||||
import { BLURHASH_FIELD } from "../../../utils/image-media";
|
import { BLURHASH_FIELD } from "../../../utils/image-media";
|
||||||
import { IMediaEventContent } from "../../../customisations/models/IMediaEventContent";
|
|
||||||
|
|
||||||
export default class MStickerBody extends MImageBody {
|
export default class MStickerBody extends MImageBody {
|
||||||
// Mostly empty to prevent default behaviour of MImageBody
|
// Mostly empty to prevent default behaviour of MImageBody
|
||||||
|
@ -80,7 +80,7 @@ export default class MStickerBody extends MImageBody {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getBanner(content: IMediaEventContent): ReactNode {
|
protected getBanner(content: MediaEventContent): ReactNode {
|
||||||
return null; // we don't need a banner, we have a tooltip
|
return null; // we don't need a banner, we have a tooltip
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ limitations under the License.
|
||||||
|
|
||||||
import React, { ReactNode } from "react";
|
import React, { ReactNode } from "react";
|
||||||
import { decode } from "blurhash";
|
import { decode } from "blurhash";
|
||||||
|
import { MediaEventContent } from "matrix-js-sdk/src/types";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
|
||||||
import { _t } from "../../../languageHandler";
|
import { _t } from "../../../languageHandler";
|
||||||
|
@ -23,7 +24,6 @@ import SettingsStore from "../../../settings/SettingsStore";
|
||||||
import InlineSpinner from "../elements/InlineSpinner";
|
import InlineSpinner from "../elements/InlineSpinner";
|
||||||
import { mediaFromContent } from "../../../customisations/Media";
|
import { mediaFromContent } from "../../../customisations/Media";
|
||||||
import { BLURHASH_FIELD } from "../../../utils/image-media";
|
import { BLURHASH_FIELD } from "../../../utils/image-media";
|
||||||
import { IMediaEventContent } from "../../../customisations/models/IMediaEventContent";
|
|
||||||
import { IBodyProps } from "./IBodyProps";
|
import { IBodyProps } from "./IBodyProps";
|
||||||
import MFileBody from "./MFileBody";
|
import MFileBody from "./MFileBody";
|
||||||
import { ImageSize, suggestedSize as suggestedVideoSize } from "../../../settings/enums/ImageSize";
|
import { ImageSize, suggestedSize as suggestedVideoSize } from "../../../settings/enums/ImageSize";
|
||||||
|
@ -62,7 +62,7 @@ export default class MVideoBody extends React.PureComponent<IBodyProps, IState>
|
||||||
}
|
}
|
||||||
|
|
||||||
private getContentUrl(): string | undefined {
|
private getContentUrl(): string | undefined {
|
||||||
const content = this.props.mxEvent.getContent<IMediaEventContent>();
|
const content = this.props.mxEvent.getContent<MediaEventContent>();
|
||||||
// During export, the content url will point to the MSC, which will later point to a local url
|
// During export, the content url will point to the MSC, which will later point to a local url
|
||||||
if (this.props.forExport) return content.file?.url ?? content.url;
|
if (this.props.forExport) return content.file?.url ?? content.url;
|
||||||
const media = mediaFromContent(content);
|
const media = mediaFromContent(content);
|
||||||
|
@ -82,7 +82,7 @@ export default class MVideoBody extends React.PureComponent<IBodyProps, IState>
|
||||||
// there's no need of thumbnail when the content is local
|
// there's no need of thumbnail when the content is local
|
||||||
if (this.props.forExport) return null;
|
if (this.props.forExport) return null;
|
||||||
|
|
||||||
const content = this.props.mxEvent.getContent<IMediaEventContent>();
|
const content = this.props.mxEvent.getContent<MediaEventContent>();
|
||||||
const media = mediaFromContent(content);
|
const media = mediaFromContent(content);
|
||||||
|
|
||||||
if (media.isEncrypted && this.state.decryptedThumbnailUrl) {
|
if (media.isEncrypted && this.state.decryptedThumbnailUrl) {
|
||||||
|
@ -121,7 +121,7 @@ export default class MVideoBody extends React.PureComponent<IBodyProps, IState>
|
||||||
posterLoading: true,
|
posterLoading: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const content = this.props.mxEvent.getContent<IMediaEventContent>();
|
const content = this.props.mxEvent.getContent<MediaEventContent>();
|
||||||
const media = mediaFromContent(content);
|
const media = mediaFromContent(content);
|
||||||
if (media.hasThumbnail) {
|
if (media.hasThumbnail) {
|
||||||
const image = new Image();
|
const image = new Image();
|
||||||
|
@ -157,7 +157,7 @@ export default class MVideoBody extends React.PureComponent<IBodyProps, IState>
|
||||||
this.props.onHeightChanged?.();
|
this.props.onHeightChanged?.();
|
||||||
} else {
|
} else {
|
||||||
logger.log("NOT preloading video");
|
logger.log("NOT preloading video");
|
||||||
const content = this.props.mxEvent.getContent<IMediaEventContent>();
|
const content = this.props.mxEvent.getContent<MediaEventContent>();
|
||||||
|
|
||||||
let mimetype = content?.info?.mimetype;
|
let mimetype = content?.info?.mimetype;
|
||||||
|
|
||||||
|
|
|
@ -15,10 +15,11 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { MatrixClient, ResizeMethod } from "matrix-js-sdk/src/matrix";
|
import { MatrixClient, ResizeMethod } from "matrix-js-sdk/src/matrix";
|
||||||
|
import { MediaEventContent } from "matrix-js-sdk/src/types";
|
||||||
import { Optional } from "matrix-events-sdk";
|
import { Optional } from "matrix-events-sdk";
|
||||||
|
|
||||||
import { MatrixClientPeg } from "../MatrixClientPeg";
|
import { MatrixClientPeg } from "../MatrixClientPeg";
|
||||||
import { IMediaEventContent, IPreparedMedia, prepEventContentAsMedia } from "./models/IMediaEventContent";
|
import { IPreparedMedia, prepEventContentAsMedia } from "./models/IMediaEventContent";
|
||||||
import { UserFriendlyError } from "../languageHandler";
|
import { UserFriendlyError } from "../languageHandler";
|
||||||
|
|
||||||
// Populate this class with the details of your customisations when copying it.
|
// Populate this class with the details of your customisations when copying it.
|
||||||
|
@ -154,11 +155,11 @@ export class Media {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a media object from event content.
|
* Creates a media object from event content.
|
||||||
* @param {IMediaEventContent} content The event content.
|
* @param {MediaEventContent} content The event content.
|
||||||
* @param {MatrixClient} client? Optional client to use.
|
* @param {MatrixClient} client? Optional client to use.
|
||||||
* @returns {Media} The media object.
|
* @returns {Media} The media object.
|
||||||
*/
|
*/
|
||||||
export function mediaFromContent(content: Partial<IMediaEventContent>, client?: MatrixClient): Media {
|
export function mediaFromContent(content: Partial<MediaEventContent>, client?: MatrixClient): Media {
|
||||||
return new Media(prepEventContentAsMedia(content), client);
|
return new Media(prepEventContentAsMedia(content), client);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,220 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// TODO: These types should be elsewhere.
|
import { EncryptedFile, MediaEventContent } from "matrix-js-sdk/src/types";
|
||||||
|
|
||||||
import { MsgType } from "matrix-js-sdk/src/matrix";
|
|
||||||
|
|
||||||
import { BLURHASH_FIELD } from "../../utils/image-media";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see https://spec.matrix.org/v1.7/client-server-api/#extensions-to-mroommessage-msgtypes
|
|
||||||
*/
|
|
||||||
export interface EncryptedFile {
|
|
||||||
/**
|
|
||||||
* The URL to the file.
|
|
||||||
*/
|
|
||||||
url: string;
|
|
||||||
/**
|
|
||||||
* A JSON Web Key object.
|
|
||||||
*/
|
|
||||||
key: {
|
|
||||||
alg: string;
|
|
||||||
key_ops: string[]; // eslint-disable-line camelcase
|
|
||||||
kty: string;
|
|
||||||
k: string;
|
|
||||||
ext: boolean;
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* The 128-bit unique counter block used by AES-CTR, encoded as unpadded base64.
|
|
||||||
*/
|
|
||||||
iv: string;
|
|
||||||
/**
|
|
||||||
* A map from an algorithm name to a hash of the ciphertext, encoded as unpadded base64.
|
|
||||||
* Clients should support the SHA-256 hash, which uses the key sha256.
|
|
||||||
*/
|
|
||||||
hashes: { [alg: string]: string };
|
|
||||||
/**
|
|
||||||
* Version of the encrypted attachment's protocol. Must be v2.
|
|
||||||
*/
|
|
||||||
v: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ThumbnailInfo {
|
|
||||||
/**
|
|
||||||
* The mimetype of the image, e.g. image/jpeg.
|
|
||||||
*/
|
|
||||||
mimetype?: string;
|
|
||||||
/**
|
|
||||||
* The intended display width of the image in pixels.
|
|
||||||
* This may differ from the intrinsic dimensions of the image file.
|
|
||||||
*/
|
|
||||||
w?: number;
|
|
||||||
/**
|
|
||||||
* The intended display height of the image in pixels.
|
|
||||||
* This may differ from the intrinsic dimensions of the image file.
|
|
||||||
*/
|
|
||||||
h?: number;
|
|
||||||
/**
|
|
||||||
* Size of the image in bytes.
|
|
||||||
*/
|
|
||||||
size?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface BaseInfo {
|
|
||||||
mimetype?: string;
|
|
||||||
size?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see https://spec.matrix.org/v1.7/client-server-api/#mfile
|
|
||||||
*/
|
|
||||||
export interface FileInfo extends BaseInfo {
|
|
||||||
/**
|
|
||||||
* @see https://github.com/matrix-org/matrix-spec-proposals/pull/2448
|
|
||||||
*/
|
|
||||||
[BLURHASH_FIELD]?: string;
|
|
||||||
/**
|
|
||||||
* Information on the encrypted thumbnail file, as specified in End-to-end encryption.
|
|
||||||
* Only present if the thumbnail is encrypted.
|
|
||||||
* @see https://spec.matrix.org/v1.7/client-server-api/#sending-encrypted-attachments
|
|
||||||
*/
|
|
||||||
thumbnail_file?: EncryptedFile;
|
|
||||||
/**
|
|
||||||
* Metadata about the image referred to in thumbnail_url.
|
|
||||||
*/
|
|
||||||
thumbnail_info?: ThumbnailInfo;
|
|
||||||
/**
|
|
||||||
* The URL to the thumbnail of the file. Only present if the thumbnail is unencrypted.
|
|
||||||
*/
|
|
||||||
thumbnail_url?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see https://spec.matrix.org/v1.7/client-server-api/#mimage
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
export interface ImageInfo extends FileInfo, ThumbnailInfo {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see https://spec.matrix.org/v1.7/client-server-api/#mimage
|
|
||||||
*/
|
|
||||||
export interface AudioInfo extends BaseInfo {
|
|
||||||
/**
|
|
||||||
* The duration of the audio in milliseconds.
|
|
||||||
*/
|
|
||||||
duration?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see https://spec.matrix.org/v1.7/client-server-api/#mvideo
|
|
||||||
*/
|
|
||||||
export interface VideoInfo extends AudioInfo, ImageInfo {
|
|
||||||
/**
|
|
||||||
* The duration of the video in milliseconds.
|
|
||||||
*/
|
|
||||||
duration?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type IMediaEventInfo = FileInfo | ImageInfo | AudioInfo | VideoInfo;
|
|
||||||
|
|
||||||
interface BaseContent {
|
|
||||||
/**
|
|
||||||
* Required if the file is encrypted. Information on the encrypted file, as specified in End-to-end encryption.
|
|
||||||
* @see https://spec.matrix.org/v1.7/client-server-api/#sending-encrypted-attachments
|
|
||||||
*/
|
|
||||||
file?: EncryptedFile;
|
|
||||||
/**
|
|
||||||
* Required if the file is unencrypted. The URL (typically mxc:// URI) to the file.
|
|
||||||
*/
|
|
||||||
url?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see https://spec.matrix.org/v1.7/client-server-api/#mfile
|
|
||||||
*/
|
|
||||||
export interface FileContent extends BaseContent {
|
|
||||||
/**
|
|
||||||
* A human-readable description of the file.
|
|
||||||
* This is recommended to be the filename of the original upload.
|
|
||||||
*/
|
|
||||||
body: string;
|
|
||||||
/**
|
|
||||||
* The original filename of the uploaded file.
|
|
||||||
*/
|
|
||||||
filename?: string;
|
|
||||||
/**
|
|
||||||
* Information about the file referred to in url.
|
|
||||||
*/
|
|
||||||
info?: FileInfo;
|
|
||||||
/**
|
|
||||||
* One of: [m.file].
|
|
||||||
*/
|
|
||||||
msgtype: MsgType.File;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see https://spec.matrix.org/v1.7/client-server-api/#mimage
|
|
||||||
*/
|
|
||||||
export interface ImageContent extends BaseContent {
|
|
||||||
/**
|
|
||||||
* A textual representation of the image.
|
|
||||||
* This could be the alt text of the image, the filename of the image,
|
|
||||||
* or some kind of content description for accessibility e.g. ‘image attachment’.
|
|
||||||
*/
|
|
||||||
body: string;
|
|
||||||
/**
|
|
||||||
* Metadata about the image referred to in url.
|
|
||||||
*/
|
|
||||||
info?: ImageInfo;
|
|
||||||
/**
|
|
||||||
* One of: [m.image].
|
|
||||||
*/
|
|
||||||
msgtype: MsgType.Image;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see https://spec.matrix.org/v1.7/client-server-api/#maudio
|
|
||||||
*/
|
|
||||||
export interface AudioContent extends BaseContent {
|
|
||||||
/**
|
|
||||||
* A description of the audio e.g. ‘Bee Gees - Stayin’ Alive’,
|
|
||||||
* or some kind of content description for accessibility e.g. ‘audio attachment’.
|
|
||||||
*/
|
|
||||||
body: string;
|
|
||||||
/**
|
|
||||||
* Metadata for the audio clip referred to in url.
|
|
||||||
*/
|
|
||||||
info?: AudioInfo;
|
|
||||||
/**
|
|
||||||
* One of: [m.audio].
|
|
||||||
*/
|
|
||||||
msgtype: MsgType.Audio;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see https://spec.matrix.org/v1.7/client-server-api/#mvideo
|
|
||||||
*/
|
|
||||||
export interface VideoContent extends BaseContent {
|
|
||||||
/**
|
|
||||||
* A description of the video e.g. ‘Gangnam style’,
|
|
||||||
* or some kind of content description for accessibility e.g. ‘video attachment’.
|
|
||||||
*/
|
|
||||||
body: string;
|
|
||||||
/**
|
|
||||||
* Metadata about the video clip referred to in url.
|
|
||||||
*/
|
|
||||||
info?: VideoInfo;
|
|
||||||
/**
|
|
||||||
* One of: [m.video].
|
|
||||||
*/
|
|
||||||
msgtype: MsgType.Video;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Type representing media event contents for `m.room.message` events listed in the Matrix specification
|
|
||||||
*/
|
|
||||||
export type IMediaEventContent = FileContent | ImageContent | AudioContent | VideoContent;
|
|
||||||
|
|
||||||
export interface IPreparedMedia extends IMediaObject {
|
export interface IPreparedMedia extends IMediaObject {
|
||||||
thumbnail?: IMediaObject;
|
thumbnail?: IMediaObject;
|
||||||
|
@ -241,11 +28,11 @@ export interface IMediaObject {
|
||||||
/**
|
/**
|
||||||
* Parses an event content body into a prepared media object. This prepared media object
|
* Parses an event content body into a prepared media object. This prepared media object
|
||||||
* can be used with other functions to manipulate the media.
|
* can be used with other functions to manipulate the media.
|
||||||
* @param {IMediaEventContent} content Unredacted media event content. See interface.
|
* @param {MediaEventContent} content Unredacted media event content. See interface.
|
||||||
* @returns {IPreparedMedia} A prepared media object.
|
* @returns {IPreparedMedia} A prepared media object.
|
||||||
* @throws Throws if the given content cannot be packaged into a prepared media object.
|
* @throws Throws if the given content cannot be packaged into a prepared media object.
|
||||||
*/
|
*/
|
||||||
export function prepEventContentAsMedia(content: Partial<IMediaEventContent>): IPreparedMedia {
|
export function prepEventContentAsMedia(content: Partial<MediaEventContent>): IPreparedMedia {
|
||||||
let thumbnail: IMediaObject | undefined;
|
let thumbnail: IMediaObject | undefined;
|
||||||
if (typeof content?.info === "object" && "thumbnail_url" in content.info && content.info.thumbnail_url) {
|
if (typeof content?.info === "object" && "thumbnail_url" in content.info && content.info.thumbnail_url) {
|
||||||
thumbnail = {
|
thumbnail = {
|
||||||
|
|
|
@ -15,8 +15,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { IEventRelation, UploadProgress } from "matrix-js-sdk/src/matrix";
|
import { IEventRelation, UploadProgress } from "matrix-js-sdk/src/matrix";
|
||||||
|
import { EncryptedFile } from "matrix-js-sdk/src/types";
|
||||||
import { EncryptedFile } from "../customisations/models/IMediaEventContent";
|
|
||||||
|
|
||||||
export class RoomUpload {
|
export class RoomUpload {
|
||||||
public readonly abortController = new AbortController();
|
public readonly abortController = new AbortController();
|
||||||
|
|
|
@ -17,9 +17,9 @@ limitations under the License.
|
||||||
// Pull in the encryption lib so that we can decrypt attachments.
|
// Pull in the encryption lib so that we can decrypt attachments.
|
||||||
import encrypt from "matrix-encrypt-attachment";
|
import encrypt from "matrix-encrypt-attachment";
|
||||||
import { parseErrorResponse } from "matrix-js-sdk/src/matrix";
|
import { parseErrorResponse } from "matrix-js-sdk/src/matrix";
|
||||||
|
import { EncryptedFile, MediaEventInfo } from "matrix-js-sdk/src/types";
|
||||||
|
|
||||||
import { mediaFromContent } from "../customisations/Media";
|
import { mediaFromContent } from "../customisations/Media";
|
||||||
import { EncryptedFile, IMediaEventInfo } from "../customisations/models/IMediaEventContent";
|
|
||||||
import { getBlobSafeMimeType } from "./blobs";
|
import { getBlobSafeMimeType } from "./blobs";
|
||||||
|
|
||||||
export class DownloadError extends Error {
|
export class DownloadError extends Error {
|
||||||
|
@ -44,10 +44,10 @@ export class DecryptError extends Error {
|
||||||
* This passed to [link]{@link https://github.com/matrix-org/matrix-encrypt-attachment}
|
* This passed to [link]{@link https://github.com/matrix-org/matrix-encrypt-attachment}
|
||||||
* as the encryption info object, so will also have the those keys in addition to
|
* as the encryption info object, so will also have the those keys in addition to
|
||||||
* the keys below.
|
* the keys below.
|
||||||
* @param {IMediaEventInfo} info The info parameter taken from the matrix event.
|
* @param {MediaEventInfo} info The info parameter taken from the matrix event.
|
||||||
* @returns {Promise<Blob>} Resolves to a Blob of the file.
|
* @returns {Promise<Blob>} Resolves to a Blob of the file.
|
||||||
*/
|
*/
|
||||||
export async function decryptFile(file?: EncryptedFile, info?: IMediaEventInfo): Promise<Blob> {
|
export async function decryptFile(file?: EncryptedFile, info?: MediaEventInfo): Promise<Blob> {
|
||||||
// throws if file is falsy
|
// throws if file is falsy
|
||||||
const media = mediaFromContent({ file });
|
const media = mediaFromContent({ file });
|
||||||
|
|
||||||
|
|
|
@ -25,22 +25,22 @@ import {
|
||||||
FileSizeReturnArray,
|
FileSizeReturnArray,
|
||||||
FileSizeReturnObject,
|
FileSizeReturnObject,
|
||||||
} from "filesize";
|
} from "filesize";
|
||||||
|
import { MediaEventContent } from "matrix-js-sdk/src/types";
|
||||||
|
|
||||||
import { IMediaEventContent } from "../customisations/models/IMediaEventContent";
|
|
||||||
import { _t } from "../languageHandler";
|
import { _t } from "../languageHandler";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts a human-readable label for the file attachment to use as
|
* Extracts a human-readable label for the file attachment to use as
|
||||||
* link text.
|
* link text.
|
||||||
*
|
*
|
||||||
* @param {IMediaEventContent} content The "content" key of the matrix event.
|
* @param {MediaEventContent} content The "content" key of the matrix event.
|
||||||
* @param {string} fallbackText The fallback text
|
* @param {string} fallbackText The fallback text
|
||||||
* @param {boolean} withSize Whether to include size information. Default true.
|
* @param {boolean} withSize Whether to include size information. Default true.
|
||||||
* @param {boolean} shortened Ensure the extension of the file name is visible. Default false.
|
* @param {boolean} shortened Ensure the extension of the file name is visible. Default false.
|
||||||
* @return {string} the human-readable link text for the attachment.
|
* @return {string} the human-readable link text for the attachment.
|
||||||
*/
|
*/
|
||||||
export function presentableTextForFile(
|
export function presentableTextForFile(
|
||||||
content: IMediaEventContent,
|
content: MediaEventContent,
|
||||||
fallbackText = _t("common|attachment"),
|
fallbackText = _t("common|attachment"),
|
||||||
withSize = true,
|
withSize = true,
|
||||||
shortened = false,
|
shortened = false,
|
||||||
|
|
|
@ -15,12 +15,12 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { MatrixEvent, EventType, MsgType } from "matrix-js-sdk/src/matrix";
|
import { MatrixEvent, EventType, MsgType } from "matrix-js-sdk/src/matrix";
|
||||||
|
import { FileContent, ImageContent, MediaEventContent } from "matrix-js-sdk/src/types";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
|
||||||
import { LazyValue } from "./LazyValue";
|
import { LazyValue } from "./LazyValue";
|
||||||
import { Media, mediaFromContent } from "../customisations/Media";
|
import { Media, mediaFromContent } from "../customisations/Media";
|
||||||
import { decryptFile } from "./DecryptFile";
|
import { decryptFile } from "./DecryptFile";
|
||||||
import { FileContent, ImageContent, IMediaEventContent } from "../customisations/models/IMediaEventContent";
|
|
||||||
import { IDestroyable } from "./IDestroyable";
|
import { IDestroyable } from "./IDestroyable";
|
||||||
|
|
||||||
// TODO: We should consider caching the blobs. https://github.com/vector-im/element-web/issues/17192
|
// TODO: We should consider caching the blobs. https://github.com/vector-im/element-web/issues/17192
|
||||||
|
@ -48,7 +48,7 @@ export class MediaEventHelper implements IDestroyable {
|
||||||
public get fileName(): string {
|
public get fileName(): string {
|
||||||
return (
|
return (
|
||||||
this.event.getContent<FileContent>().filename ||
|
this.event.getContent<FileContent>().filename ||
|
||||||
this.event.getContent<IMediaEventContent>().body ||
|
this.event.getContent<MediaEventContent>().body ||
|
||||||
"download"
|
"download"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -81,7 +81,7 @@ export class MediaEventHelper implements IDestroyable {
|
||||||
|
|
||||||
private fetchSource = (): Promise<Blob> => {
|
private fetchSource = (): Promise<Blob> => {
|
||||||
if (this.media.isEncrypted) {
|
if (this.media.isEncrypted) {
|
||||||
const content = this.event.getContent<IMediaEventContent>();
|
const content = this.event.getContent<MediaEventContent>();
|
||||||
return decryptFile(content.file!, content.info);
|
return decryptFile(content.file!, content.info);
|
||||||
}
|
}
|
||||||
return this.media.downloadSource().then((r) => r.blob());
|
return this.media.downloadSource().then((r) => r.blob());
|
||||||
|
|
|
@ -15,6 +15,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Direction, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
|
import { Direction, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
|
||||||
|
import { MediaEventContent } from "matrix-js-sdk/src/types";
|
||||||
import { saveAs } from "file-saver";
|
import { saveAs } from "file-saver";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
import sanitizeFilename from "sanitize-filename";
|
import sanitizeFilename from "sanitize-filename";
|
||||||
|
@ -24,7 +25,6 @@ import { decryptFile } from "../DecryptFile";
|
||||||
import { mediaFromContent } from "../../customisations/Media";
|
import { mediaFromContent } from "../../customisations/Media";
|
||||||
import { formatFullDateNoDay, formatFullDateNoDayISO } from "../../DateUtils";
|
import { formatFullDateNoDay, formatFullDateNoDayISO } from "../../DateUtils";
|
||||||
import { isVoiceMessage } from "../EventUtils";
|
import { isVoiceMessage } from "../EventUtils";
|
||||||
import { IMediaEventContent } from "../../customisations/models/IMediaEventContent";
|
|
||||||
import { _t } from "../../languageHandler";
|
import { _t } from "../../languageHandler";
|
||||||
import SdkConfig from "../../SdkConfig";
|
import SdkConfig from "../../SdkConfig";
|
||||||
|
|
||||||
|
@ -225,7 +225,7 @@ export default abstract class Exporter {
|
||||||
let blob: Blob | undefined = undefined;
|
let blob: Blob | undefined = undefined;
|
||||||
try {
|
try {
|
||||||
const isEncrypted = event.isEncrypted();
|
const isEncrypted = event.isEncrypted();
|
||||||
const content = event.getContent<IMediaEventContent>();
|
const content = event.getContent<MediaEventContent>();
|
||||||
const shouldDecrypt = isEncrypted && content.hasOwnProperty("file") && event.getType() !== "m.sticker";
|
const shouldDecrypt = isEncrypted && content.hasOwnProperty("file") && event.getType() !== "m.sticker";
|
||||||
if (shouldDecrypt) {
|
if (shouldDecrypt) {
|
||||||
blob = await decryptFile(content.file);
|
blob = await decryptFile(content.file);
|
||||||
|
|
|
@ -14,8 +14,9 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { EncryptedFile } from "matrix-js-sdk/src/types";
|
||||||
|
|
||||||
import { BlurhashEncoder } from "../BlurhashEncoder";
|
import { BlurhashEncoder } from "../BlurhashEncoder";
|
||||||
import { EncryptedFile } from "../customisations/models/IMediaEventContent";
|
|
||||||
|
|
||||||
type ThumbnailableElement = HTMLImageElement | HTMLVideoElement;
|
type ThumbnailableElement = HTMLImageElement | HTMLVideoElement;
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ import {
|
||||||
RelationType,
|
RelationType,
|
||||||
TypedEventEmitter,
|
TypedEventEmitter,
|
||||||
} from "matrix-js-sdk/src/matrix";
|
} from "matrix-js-sdk/src/matrix";
|
||||||
|
import { EncryptedFile } from "matrix-js-sdk/src/types";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ChunkRecordedPayload,
|
ChunkRecordedPayload,
|
||||||
|
@ -38,7 +39,6 @@ import {
|
||||||
VoiceBroadcastRecorderEvent,
|
VoiceBroadcastRecorderEvent,
|
||||||
} from "..";
|
} from "..";
|
||||||
import { uploadFile } from "../../ContentMessages";
|
import { uploadFile } from "../../ContentMessages";
|
||||||
import { EncryptedFile } from "../../customisations/models/IMediaEventContent";
|
|
||||||
import { createVoiceMessageContent } from "../../utils/createVoiceMessageContent";
|
import { createVoiceMessageContent } from "../../utils/createVoiceMessageContent";
|
||||||
import { IDestroyable } from "../../utils/IDestroyable";
|
import { IDestroyable } from "../../utils/IDestroyable";
|
||||||
import dis from "../../dispatcher/dispatcher";
|
import dis from "../../dispatcher/dispatcher";
|
||||||
|
|
|
@ -29,9 +29,9 @@ import {
|
||||||
Relations,
|
Relations,
|
||||||
SyncState,
|
SyncState,
|
||||||
} from "matrix-js-sdk/src/matrix";
|
} from "matrix-js-sdk/src/matrix";
|
||||||
|
import { EncryptedFile } from "matrix-js-sdk/src/types";
|
||||||
|
|
||||||
import { uploadFile } from "../../../src/ContentMessages";
|
import { uploadFile } from "../../../src/ContentMessages";
|
||||||
import { EncryptedFile } from "../../../src/customisations/models/IMediaEventContent";
|
|
||||||
import { createVoiceMessageContent } from "../../../src/utils/createVoiceMessageContent";
|
import { createVoiceMessageContent } from "../../../src/utils/createVoiceMessageContent";
|
||||||
import {
|
import {
|
||||||
createVoiceBroadcastRecorder,
|
createVoiceBroadcastRecorder,
|
||||||
|
|
Loading…
Reference in a new issue