mirror of
https://github.com/vector-im/element-call.git
synced 2026-03-28 06:50:26 +00:00
Download Avatar from relevent source
Instead of relying on failures directly use the available method to download the avatar.
This commit is contained in:
@@ -17,8 +17,15 @@ import EventEmitter from "events";
|
||||
import { widget } from "./widget";
|
||||
|
||||
const TestComponent: FC<
|
||||
PropsWithChildren<{ client: MatrixClient; supportsThumbnails?: boolean }>
|
||||
> = ({ client, children, supportsThumbnails }) => {
|
||||
PropsWithChildren<{
|
||||
client: MatrixClient;
|
||||
supportsAuthenticatedMedia?: boolean;
|
||||
}>
|
||||
> = ({
|
||||
client,
|
||||
children,
|
||||
supportsAuthenticatedMedia: supportsAuthenticatedMedia,
|
||||
}) => {
|
||||
return (
|
||||
<ClientContextProvider
|
||||
value={{
|
||||
@@ -26,7 +33,7 @@ const TestComponent: FC<
|
||||
disconnected: false,
|
||||
supportedFeatures: {
|
||||
reactions: true,
|
||||
thumbnails: supportsThumbnails ?? true,
|
||||
authenticatedMedia: supportsAuthenticatedMedia ?? true,
|
||||
},
|
||||
setClient: vi.fn(),
|
||||
authenticated: {
|
||||
@@ -81,36 +88,7 @@ test("should just render a placeholder when the user has no avatar", () => {
|
||||
expect(client.mxcUrlToHttp).toBeCalledTimes(0);
|
||||
});
|
||||
|
||||
test("should just render a placeholder when thumbnails are not supported", () => {
|
||||
const client = vi.mocked<MatrixClient>({
|
||||
getAccessToken: () => "my-access-token",
|
||||
mxcUrlToHttp: () => vi.fn(),
|
||||
} as unknown as MatrixClient);
|
||||
|
||||
vi.spyOn(client, "mxcUrlToHttp");
|
||||
const member = mockMatrixRoomMember(
|
||||
mockRtcMembership("@alice:example.org", "AAAA"),
|
||||
{
|
||||
getMxcAvatarUrl: () => "mxc://example.org/alice-avatar",
|
||||
},
|
||||
);
|
||||
const displayName = "Alice";
|
||||
render(
|
||||
<TestComponent client={client} supportsThumbnails={false}>
|
||||
<Avatar
|
||||
id={member.userId}
|
||||
name={displayName}
|
||||
size={96}
|
||||
src={member.getMxcAvatarUrl()}
|
||||
/>
|
||||
</TestComponent>,
|
||||
);
|
||||
const element = screen.getByRole("img", { name: "@alice:example.org" });
|
||||
expect(element.tagName).toEqual("SPAN");
|
||||
expect(client.mxcUrlToHttp).toBeCalledTimes(0);
|
||||
});
|
||||
|
||||
test("should attempt to fetch authenticated media", async () => {
|
||||
test("should attempt to fetch authenticated media if supported", async () => {
|
||||
const expectedAuthUrl = "http://example.org/media/alice-avatar";
|
||||
const expectedObjectURL = "my-object-url";
|
||||
const accessToken = "my-access-token";
|
||||
@@ -142,7 +120,7 @@ test("should attempt to fetch authenticated media", async () => {
|
||||
);
|
||||
const displayName = "Alice";
|
||||
render(
|
||||
<TestComponent client={client}>
|
||||
<TestComponent client={client} supportsAuthenticatedMedia={true}>
|
||||
<Avatar
|
||||
id={member.userId}
|
||||
name={displayName}
|
||||
@@ -163,7 +141,7 @@ test("should attempt to fetch authenticated media", async () => {
|
||||
});
|
||||
});
|
||||
|
||||
test("should use widget API when unable to authenticate media", async () => {
|
||||
test("should attempt to use widget API if authenticate media is not supported", async () => {
|
||||
const expectedMXCUrl = "mxc://example.org/alice-avatar";
|
||||
const expectedObjectURL = "my-object-url";
|
||||
const theBlob = new Blob([]);
|
||||
@@ -188,7 +166,7 @@ test("should use widget API when unable to authenticate media", async () => {
|
||||
);
|
||||
const displayName = "Alice";
|
||||
render(
|
||||
<TestComponent client={client}>
|
||||
<TestComponent client={client} supportsAuthenticatedMedia={false}>
|
||||
<Avatar
|
||||
id={member.userId}
|
||||
name={displayName}
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
import { Avatar as CompoundAvatar } from "@vector-im/compound-web";
|
||||
import { type MatrixClient } from "matrix-js-sdk";
|
||||
|
||||
import { ClientState, useClientState } from "./ClientContext";
|
||||
import { useClientState } from "./ClientContext";
|
||||
import { widget } from "./widget";
|
||||
import { WidgetApi } from "matrix-widget-api";
|
||||
|
||||
@@ -80,7 +80,7 @@ export const Avatar: FC<Props> = ({
|
||||
const sizePx = useMemo(
|
||||
() =>
|
||||
Object.values(Size).includes(size as Size)
|
||||
? sizes.get(size as Size)
|
||||
? sizes.get(size as Size)!
|
||||
: (size as number),
|
||||
[size],
|
||||
);
|
||||
@@ -88,17 +88,29 @@ export const Avatar: FC<Props> = ({
|
||||
const [avatarUrl, setAvatarUrl] = useState<string | undefined>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
if (!src) {
|
||||
if (!src || clientState?.state !== "valid") {
|
||||
setAvatarUrl(undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
const { authenticated, supportedFeatures } = clientState;
|
||||
let blob: Promise<Blob>;
|
||||
|
||||
if (
|
||||
supportedFeatures.authenticatedMedia &&
|
||||
authenticated?.client &&
|
||||
sizePx
|
||||
) {
|
||||
blob = getAvatarFromServer(authenticated.client, src, sizePx);
|
||||
} else if (widget?.api) {
|
||||
blob = getAvatarFromWidgetAPI(widget.api, src);
|
||||
} else {
|
||||
setAvatarUrl(undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
let objectUrl: string | undefined;
|
||||
|
||||
getAvatarFromServer(clientState, src, sizePx) // Try to download directly from the mxc://
|
||||
.catch((ex) => {
|
||||
return getAvatarFromWidget(widget?.api, src); // Fallback to trying to use the MSC4039 Widget API
|
||||
})
|
||||
blob
|
||||
.then((blob) => {
|
||||
objectUrl = URL.createObjectURL(blob);
|
||||
setAvatarUrl(objectUrl);
|
||||
@@ -128,26 +140,10 @@ export const Avatar: FC<Props> = ({
|
||||
};
|
||||
|
||||
async function getAvatarFromServer(
|
||||
clientState: ClientState | undefined,
|
||||
client: MatrixClient,
|
||||
src: string,
|
||||
sizePx: number | undefined,
|
||||
sizePx: number,
|
||||
): Promise<Blob> {
|
||||
if (clientState?.state !== "valid") {
|
||||
throw new Error("Client state must be valid");
|
||||
}
|
||||
if (!sizePx) {
|
||||
throw new Error("size must be supplied");
|
||||
}
|
||||
|
||||
const { authenticated, supportedFeatures } = clientState;
|
||||
const client = authenticated?.client;
|
||||
if (!client) {
|
||||
throw new Error("Client must be supplied");
|
||||
}
|
||||
if (!supportedFeatures.thumbnails) {
|
||||
throw new Error("Thumbnails are not supported");
|
||||
}
|
||||
|
||||
const httpSrc = getAvatarUrl(client, src, sizePx);
|
||||
if (!httpSrc) {
|
||||
throw new Error("Failed to get http avatar URL");
|
||||
@@ -169,14 +165,10 @@ async function getAvatarFromServer(
|
||||
return blob;
|
||||
}
|
||||
|
||||
async function getAvatarFromWidget(
|
||||
api: WidgetApi | undefined,
|
||||
async function getAvatarFromWidgetAPI(
|
||||
api: WidgetApi,
|
||||
src: string,
|
||||
): Promise<Blob> {
|
||||
if (!api) {
|
||||
throw new Error("No widget api given");
|
||||
}
|
||||
|
||||
const response = await api.downloadFile(src);
|
||||
const file = response.file;
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ export type ValidClientState = {
|
||||
disconnected: boolean;
|
||||
supportedFeatures: {
|
||||
reactions: boolean;
|
||||
thumbnails: boolean;
|
||||
authenticatedMedia: boolean;
|
||||
};
|
||||
setClient: (client: MatrixClient, session: Session) => void;
|
||||
};
|
||||
@@ -249,7 +249,8 @@ export const ClientProvider: FC<Props> = ({ children }) => {
|
||||
|
||||
const [isDisconnected, setIsDisconnected] = useState(false);
|
||||
const [supportsReactions, setSupportsReactions] = useState(false);
|
||||
const [supportsThumbnails, setSupportsThumbnails] = useState(false);
|
||||
const [supportsAuthenticatedMedia, setSupportsAuthenticatedMedia] =
|
||||
useState(false);
|
||||
|
||||
const state: ClientState | undefined = useMemo(() => {
|
||||
if (alreadyOpenedErr) {
|
||||
@@ -275,7 +276,7 @@ export const ClientProvider: FC<Props> = ({ children }) => {
|
||||
disconnected: isDisconnected,
|
||||
supportedFeatures: {
|
||||
reactions: supportsReactions,
|
||||
thumbnails: supportsThumbnails,
|
||||
authenticatedMedia: supportsAuthenticatedMedia,
|
||||
},
|
||||
};
|
||||
}, [
|
||||
@@ -286,7 +287,7 @@ export const ClientProvider: FC<Props> = ({ children }) => {
|
||||
setClient,
|
||||
isDisconnected,
|
||||
supportsReactions,
|
||||
supportsThumbnails,
|
||||
supportsAuthenticatedMedia,
|
||||
]);
|
||||
|
||||
const onSync = useCallback(
|
||||
@@ -312,8 +313,8 @@ export const ClientProvider: FC<Props> = ({ children }) => {
|
||||
}
|
||||
|
||||
if (initClientState.widgetApi) {
|
||||
// There is currently no widget API for authenticated media thumbnails.
|
||||
setSupportsThumbnails(false);
|
||||
// There is currently no way for widgets to request authenticated media directly from the server.
|
||||
setSupportsAuthenticatedMedia(false);
|
||||
const reactSend = initClientState.widgetApi.hasCapability(
|
||||
"org.matrix.msc2762.send.event:m.reaction",
|
||||
);
|
||||
@@ -335,7 +336,7 @@ export const ClientProvider: FC<Props> = ({ children }) => {
|
||||
}
|
||||
} else {
|
||||
setSupportsReactions(true);
|
||||
setSupportsThumbnails(true);
|
||||
setSupportsAuthenticatedMedia(true);
|
||||
}
|
||||
|
||||
return (): void => {
|
||||
|
||||
@@ -78,7 +78,7 @@ function renderWithMockClient(
|
||||
disconnected: false,
|
||||
supportedFeatures: {
|
||||
reactions: true,
|
||||
thumbnails: true,
|
||||
authenticatedMedia: true,
|
||||
},
|
||||
setClient: vi.fn(),
|
||||
authenticated: {
|
||||
|
||||
Reference in New Issue
Block a user