Early proof of concept for media customization support
This commit is contained in:
parent
bec6697f36
commit
93f7f13c44
3 changed files with 228 additions and 2 deletions
138
src/customisations/Media.ts
Normal file
138
src/customisations/Media.ts
Normal file
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import {MatrixClientPeg} from "../MatrixClientPeg";
|
||||
import {IMediaEventContent, IPreparedMedia, prepEventContentAsMedia} from "./models/IMediaEventContent";
|
||||
|
||||
// Populate this class with the details of your customisations when copying it.
|
||||
|
||||
// Implementation note: The Media class must complete the contract as shown here, though
|
||||
// the constructor can be whatever is relevant to your implementation. The mediaForX
|
||||
// functions below create an instance of the Media class and are used throughout the
|
||||
// project.
|
||||
|
||||
/**
|
||||
* A media object is a representation of a "source media" and an optional
|
||||
* "thumbnail media", derived from event contents or external sources.
|
||||
*/
|
||||
export class Media {
|
||||
// Per above, this constructor signature can be whatever is helpful for you.
|
||||
constructor(private prepared: IPreparedMedia) {
|
||||
}
|
||||
|
||||
/**
|
||||
* The MXC URI of the source media.
|
||||
*/
|
||||
public get srcMxc(): string {
|
||||
return this.prepared.mxc;
|
||||
}
|
||||
|
||||
/**
|
||||
* The MXC URI of the thumbnail media, if a thumbnail is recorded. Null/undefined
|
||||
* otherwise.
|
||||
*/
|
||||
public get thumbnailMxc(): string | undefined | null {
|
||||
return this.prepared.thumbnail?.mxc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not a thumbnail is recorded for this media.
|
||||
*/
|
||||
public get hasThumbnail(): boolean {
|
||||
return !!this.thumbnailMxc;
|
||||
}
|
||||
|
||||
/**
|
||||
* The HTTP URL for the source media.
|
||||
*/
|
||||
public get srcHttp(): string {
|
||||
return MatrixClientPeg.get().mxcUrlToHttp(this.srcMxc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the HTTP URL for the thumbnail media with the requested characteristics, if a thumbnail
|
||||
* is recorded for this media. Returns null/undefined otherwise.
|
||||
* @param {number} width The desired width of the thumbnail.
|
||||
* @param {number} height The desired height of the thumbnail.
|
||||
* @param {"scale"|"crop"} mode The desired thumbnailing mode. Defaults to scale.
|
||||
* @returns {string} The HTTP URL which points to the thumbnail.
|
||||
*/
|
||||
public getThumbnailHttp(width: number, height: number, mode: 'scale' | 'crop' = "scale"): string | null | undefined {
|
||||
if (!this.hasThumbnail) return null;
|
||||
return MatrixClientPeg.get().mxcUrlToHttp(this.thumbnailMxc, width, height, mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the HTTP URL for a thumbnail of the source media with the requested characteristics.
|
||||
* @param {number} width The desired width of the thumbnail.
|
||||
* @param {number} height The desired height of the thumbnail.
|
||||
* @param {"scale"|"crop"} mode The desired thumbnailing mode. Defaults to scale.
|
||||
* @returns {string} The HTTP URL which points to the thumbnail.
|
||||
*/
|
||||
public getThumbnailOfSourceHttp(width: number, height: number, mode: 'scale' | 'crop' = "scale"): string {
|
||||
return MatrixClientPeg.get().mxcUrlToHttp(this.srcMxc, width, height, mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads the source media.
|
||||
* @returns {Promise<Response>} Resolves to the server's response for chaining.
|
||||
*/
|
||||
public downloadSource(): Promise<Response> {
|
||||
return fetch(this.srcHttp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads the thumbnail media with the requested characteristics. If no thumbnail media is present,
|
||||
* this throws an exception.
|
||||
* @param {number} width The desired width of the thumbnail.
|
||||
* @param {number} height The desired height of the thumbnail.
|
||||
* @param {"scale"|"crop"} mode The desired thumbnailing mode. Defaults to scale.
|
||||
* @returns {Promise<Response>} Resolves to the server's response for chaining.
|
||||
*/
|
||||
public downloadThumbnail(width: number, height: number, mode: 'scale' | 'crop' = "scale"): Promise<Response> {
|
||||
if (!this.hasThumbnail) throw new Error("Cannot download non-existent thumbnail");
|
||||
return fetch(this.getThumbnailHttp(width, height, mode));
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads a thumbnail of the source media with the requested characteristics.
|
||||
* @param {number} width The desired width of the thumbnail.
|
||||
* @param {number} height The desired height of the thumbnail.
|
||||
* @param {"scale"|"crop"} mode The desired thumbnailing mode. Defaults to scale.
|
||||
* @returns {Promise<Response>} Resolves to the server's response for chaining.
|
||||
*/
|
||||
public downloadThumbnailOfSource(width: number, height: number, mode: 'scale' | 'crop' = "scale"): Promise<Response> {
|
||||
return fetch(this.getThumbnailOfSourceHttp(width, height, mode));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a media object from event content.
|
||||
* @param {IMediaEventContent} content The event content.
|
||||
* @returns {Media} The media object.
|
||||
*/
|
||||
export function mediaFromContent(content: IMediaEventContent): Media {
|
||||
return new Media(prepEventContentAsMedia(content));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a media object from an MXC URI.
|
||||
* @param {string} mxc The MXC URI.
|
||||
* @returns {Media} The media object.
|
||||
*/
|
||||
export function mediaFromMxc(mxc: string): Media {
|
||||
return mediaFromContent({url: mxc});
|
||||
}
|
87
src/customisations/models/IMediaEventContent.ts
Normal file
87
src/customisations/models/IMediaEventContent.ts
Normal file
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// TODO: These types should be elsewhere.
|
||||
|
||||
export interface IEncryptedFile {
|
||||
url: string;
|
||||
key: {
|
||||
alg: string;
|
||||
key_ops: string[];
|
||||
kty: string;
|
||||
k: string;
|
||||
ext: boolean;
|
||||
};
|
||||
iv: string;
|
||||
hashes: {[alg: string]: string};
|
||||
v: string;
|
||||
}
|
||||
|
||||
export interface IMediaEventContent {
|
||||
url?: string; // required on unencrypted media
|
||||
file?: IEncryptedFile; // required for *encrypted* media
|
||||
info?: {
|
||||
thumbnail_url?: string;
|
||||
thumbnail_file?: IEncryptedFile;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IPreparedMedia extends IMediaObject {
|
||||
thumbnail?: IMediaObject;
|
||||
}
|
||||
|
||||
export interface IMediaObject {
|
||||
mxc: string;
|
||||
file?: IEncryptedFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an event content body into a prepared media object. This prepared media object
|
||||
* can be used with other functions to manipulate the media.
|
||||
* @param {IMediaEventContent} content Unredacted media event content. See interface.
|
||||
* @returns {IPreparedMedia} A prepared media object.
|
||||
* @throws Throws if the given content cannot be packaged into a prepared media object.
|
||||
*/
|
||||
export function prepEventContentAsMedia(content: IMediaEventContent): IPreparedMedia {
|
||||
let thumbnail: IMediaObject = null;
|
||||
if (content?.info?.thumbnail_url) {
|
||||
thumbnail = {
|
||||
mxc: content.info.thumbnail_url,
|
||||
file: content.info.thumbnail_file,
|
||||
};
|
||||
} else if (content?.info?.thumbnail_file?.url) {
|
||||
thumbnail = {
|
||||
mxc: content.info.thumbnail_file.url,
|
||||
file: content.info.thumbnail_file,
|
||||
};
|
||||
}
|
||||
|
||||
if (content?.url) {
|
||||
return {
|
||||
thumbnail,
|
||||
mxc: content.url,
|
||||
file: content.file,
|
||||
};
|
||||
} else if (content?.file?.url) {
|
||||
return {
|
||||
thumbnail,
|
||||
mxc: content.file.url,
|
||||
file: content.file,
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error("Invalid file provided: cannot determine MXC URI. Has it been redacted?");
|
||||
}
|
|
@ -19,6 +19,7 @@ limitations under the License.
|
|||
import encrypt from 'browser-encrypt-attachment';
|
||||
// Grab the client so that we can turn mxc:// URLs into https:// URLS.
|
||||
import {MatrixClientPeg} from '../MatrixClientPeg';
|
||||
import {mediaFromContent} from "../customisations/Media";
|
||||
|
||||
// WARNING: We have to be very careful about what mime-types we allow into blobs,
|
||||
// as for performance reasons these are now rendered via URL.createObjectURL()
|
||||
|
@ -87,9 +88,9 @@ const ALLOWED_BLOB_MIMETYPES = {
|
|||
* @returns {Promise}
|
||||
*/
|
||||
export function decryptFile(file) {
|
||||
const url = MatrixClientPeg.get().mxcUrlToHttp(file.url);
|
||||
const media = mediaFromContent({file});
|
||||
// Download the encrypted file as an array buffer.
|
||||
return Promise.resolve(fetch(url)).then(function(response) {
|
||||
return media.downloadSource().then(function(response) {
|
||||
return response.arrayBuffer();
|
||||
}).then(function(responseData) {
|
||||
// Decrypt the array buffer using the information taken from
|
||||
|
|
Loading…
Reference in a new issue