Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Commit

Permalink
Demonstrative example of MSC3916 using blobs/async auth
Browse files Browse the repository at this point in the history
  • Loading branch information
turt2live committed Apr 30, 2024
1 parent 906c9dd commit 5cdd706
Show file tree
Hide file tree
Showing 9 changed files with 128 additions and 9 deletions.
4 changes: 3 additions & 1 deletion src/components/structures/BackdropPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ limitations under the License.

import React, { CSSProperties } from "react";

import AuthedImage from "../views/elements/AuthedImage";

interface IProps {
backgroundImage?: string;
blurMultiplier?: number;
Expand All @@ -36,7 +38,7 @@ export const BackdropPanel: React.FC<IProps> = ({ backgroundImage, blurMultiplie
}
return (
<div className="mx_BackdropPanel">
<img role="presentation" alt="" style={styles} className="mx_BackdropPanel--image" src={backgroundImage} />
<AuthedImage role="presentation" alt="" style={styles} className="mx_BackdropPanel--image" src={backgroundImage} />
</div>
);
};
Expand Down
62 changes: 62 additions & 0 deletions src/components/views/elements/AuthedImage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
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 React, {useEffect} from "react";

import {canAddAuthToMediaUrl, getMediaByUrl} from "../../../utils/media";

interface IProps extends React.HTMLProps<HTMLImageElement> {
}

const AuthedImage: React.FC<IProps> = (props) => {
const [src, setSrc] = React.useState<string | undefined>("");

useEffect(() => {
let blobUrl: string | undefined;
async function getImage(): Promise<void> {
if (props.src) {
if (await canAddAuthToMediaUrl(props.src)) {
const response = await getMediaByUrl(props.src);
blobUrl = URL.createObjectURL(await response.blob());
setSrc(blobUrl);
} else {
// Skip blob caching if we're just doing a plain http(s) request.
setSrc(props.src);
}
}
}

// noinspection JSIgnoredPromiseFromCall
getImage();

return () => {
// Cleanup
if (blobUrl) {
URL.revokeObjectURL(blobUrl);
}
};
}, [props.src]);

const props2 = {...props};
props2.src = src;

return (
// eslint-disable-next-line jsx-a11y/alt-text
<img {...props2} />
);
};

export default AuthedImage;
3 changes: 2 additions & 1 deletion src/components/views/elements/ImageView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
import { presentableTextForFile } from "../../../utils/FileUtils";
import AuthedImage from "./AuthedImage";
import AccessibleButton from "./AccessibleButton";

// Max scale to keep gaps around the image
Expand Down Expand Up @@ -585,7 +586,7 @@ export default class ImageView extends React.Component<IProps, IState> {
onMouseUp={this.onEndMoving}
onMouseLeave={this.onEndMoving}
>
<img
<AuthedImage
src={this.props.src}
style={style}
alt={this.props.name}
Expand Down
20 changes: 17 additions & 3 deletions src/components/views/messages/MImageBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ import { presentableTextForFile } from "../../../utils/FileUtils";
import { createReconnectedListener } from "../../../utils/connection";
import MediaProcessingError from "./shared/MediaProcessingError";
import { DecryptError, DownloadError } from "../../../utils/DecryptFile";
import AuthedImage from "../elements/AuthedImage";
import {getMediaByUrl} from "../../../utils/media";

enum Placeholder {
NoImage,
Expand Down Expand Up @@ -301,7 +303,19 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
img.onerror = reject;
});
img.crossOrigin = "Anonymous"; // CORS allow canvas access
img.src = contentUrl ?? "";

if (contentUrl) {
const response = await getMediaByUrl(contentUrl);
const blob = await response.blob();
const blobUrl = URL.createObjectURL(blob);
img.src = blobUrl;

loadPromise.then(() => {
URL.revokeObjectURL(blobUrl);
});
} else {
img.src = "";
}

try {
await loadPromise;
Expand Down Expand Up @@ -435,7 +449,7 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
imageElement = <HiddenImagePlaceholder />;
} else {
imageElement = (
<img
<AuthedImage
style={{ display: "none" }}
src={thumbUrl}
ref={this.image}
Expand Down Expand Up @@ -478,7 +492,7 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
// which has the same width as the timeline
// mx_MImageBody_thumbnail resizes img to exactly container size
img = (
<img
<AuthedImage
className="mx_MImageBody_thumbnail"
src={thumbUrl}
ref={this.image}
Expand Down
3 changes: 2 additions & 1 deletion src/components/views/messages/ReactionsRowButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import ReactionsRowButtonTooltip from "./ReactionsRowButtonTooltip";
import AccessibleButton from "../elements/AccessibleButton";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import { REACTION_SHORTCODE_KEY } from "./ReactionsRow";
import AuthedImage from "../elements/AuthedImage";

export interface IProps {
// The event we're displaying reactions for
Expand Down Expand Up @@ -144,7 +145,7 @@ export default class ReactionsRowButton extends React.PureComponent<IProps, ISta
const imageSrc = mediaFromMxc(content).srcHttp;
if (imageSrc) {
reactionContent = (
<img
<AuthedImage
className="mx_ReactionsRowButton_content"
alt={customReactionName || _t("timeline|reactions|custom_reaction_fallback_label")}
src={imageSrc}
Expand Down
3 changes: 2 additions & 1 deletion src/components/views/rooms/LinkPreviewWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { mediaFromMxc } from "../../../customisations/Media";
import ImageView from "../elements/ImageView";
import LinkWithTooltip from "../elements/LinkWithTooltip";
import PlatformPeg from "../../../PlatformPeg";
import AuthedImage from "../elements/AuthedImage";

interface IProps {
link: string;
Expand Down Expand Up @@ -95,7 +96,7 @@ export default class LinkPreviewWidget extends React.Component<IProps> {
if (image) {
img = (
<div className="mx_LinkPreviewWidget_image" style={{ height: thumbHeight }}>
<img
<AuthedImage
ref={this.image}
style={{ maxWidth: imageMaxWidth, maxHeight: imageMaxHeight }}
src={image}
Expand Down
3 changes: 2 additions & 1 deletion src/components/views/spaces/SpaceBasicSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { _t } from "../../../languageHandler";
import AccessibleButton from "../elements/AccessibleButton";
import Field from "../elements/Field";
import { chromeFileInputFix } from "../../../utils/BrowserWorkarounds";
import AuthedImage from "../elements/AuthedImage";

interface IProps {
avatarUrl?: string;
Expand All @@ -44,7 +45,7 @@ export const SpaceAvatar: React.FC<Pick<IProps, "avatarUrl" | "avatarDisabled" |
let avatarSection;
if (avatarDisabled) {
if (avatar) {
avatarSection = <img className="mx_SpaceBasicSettings_avatar" src={avatar} alt="" />;
avatarSection = <AuthedImage className="mx_SpaceBasicSettings_avatar" src={avatar} alt="" />;
} else {
avatarSection = <div className="mx_SpaceBasicSettings_avatar" />;
}
Expand Down
3 changes: 2 additions & 1 deletion src/customisations/Media.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { Optional } from "matrix-events-sdk";
import { MatrixClientPeg } from "../MatrixClientPeg";
import { IPreparedMedia, prepEventContentAsMedia } from "./models/IMediaEventContent";
import { UserFriendlyError } from "../languageHandler";
import {getMediaByUrl} from "../utils/media";

// Populate this class with the details of your customisations when copying it.

Expand Down Expand Up @@ -149,7 +150,7 @@ export class Media {
if (!src) {
throw new UserFriendlyError("error|download_media");
}
return fetch(src);
return getMediaByUrl(src);
}
}

Expand Down
36 changes: 36 additions & 0 deletions src/utils/media.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
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 {MatrixClientPeg} from "../MatrixClientPeg";

export async function canAddAuthToMediaUrl(url: string): Promise<boolean> {
return url.includes("/_matrix/media/v3") && Boolean(await MatrixClientPeg.get()?.doesServerSupportUnstableFeature("org.matrix.msc3916"));
}

export async function getMediaByUrl(url: string): Promise<Response> {
// If the server doesn't support unstable auth, don't use it :)
if (!(await canAddAuthToMediaUrl(url))) {
return fetch(url);
}

// We can rewrite the URL to support auth now, and request accordingly.
url = url.replace(/\/media\/v3\/(.*)\//, "/client/unstable/org.matrix.msc3916/media/$1/");
return fetch(url, {
headers: {
'Authorization': `Bearer ${MatrixClientPeg.get()?.getAccessToken()}`,
},
});
}

0 comments on commit 5cdd706

Please sign in to comment.