Fix authenticated media loading for Tauri/WebKit environments

- Add useAuthenticatedMediaUrl hook that fetches media with auth headers
- Add AuthenticatedMedia, AuthenticatedAvatarImage components
- Update downloadMedia and downloadEncryptedMedia to accept access token
- Update all media components to pass access token for auth media
- Fix 401 errors on Matrix servers requiring authenticated media (v1.11+)

This fixes media loading in Tauri desktop apps where service workers
may not work reliably for cross-origin authenticated requests.
This commit is contained in:
2026-01-23 22:31:37 +11:00
parent 94f8466d1c
commit 54f5fa95a6
19 changed files with 330 additions and 38 deletions

View File

@@ -32,6 +32,32 @@ import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
import { ModalWide } from '../../../styles/Modal.css';
import { validBlurHash } from '../../../utils/blurHash';
/**
* Fetches media with authentication headers and returns a blob URL.
* This is needed because service workers don't work reliably in Tauri/WebKit.
*/
const fetchAuthenticatedMedia = async (
url: string,
accessToken: string | null
): Promise<string> => {
const response = await fetch(url, {
method: 'GET',
headers: accessToken ? { Authorization: `Bearer ${accessToken}` } : undefined,
});
if (!response.ok) {
throw new Error(`Failed to fetch media: ${response.status}`);
}
const blob = await response.blob();
return URL.createObjectURL(blob);
};
/**
* Checks if a URL is an authenticated media endpoint.
*/
const isAuthenticatedMediaUrl = (url: string): boolean =>
url.includes('/_matrix/client/v1/media/download') ||
url.includes('/_matrix/client/v1/media/thumbnail');
type RenderViewerProps = {
src: string;
alt: string;
@@ -88,12 +114,19 @@ export const ImageContent = as<'div', ImageContentProps>(
const [srcState, loadSrc] = useAsyncCallback(
useCallback(async () => {
const mediaUrl = mxcUrlToHttp(mx, url, useAuthentication) ?? url;
const accessToken = mx.getAccessToken();
if (encInfo) {
const fileContent = await downloadEncryptedMedia(mediaUrl, (encBuf) =>
decryptFile(encBuf, mimeType ?? FALLBACK_MIMETYPE, encInfo)
const fileContent = await downloadEncryptedMedia(
mediaUrl,
(encBuf) => decryptFile(encBuf, mimeType ?? FALLBACK_MIMETYPE, encInfo),
accessToken
);
return URL.createObjectURL(fileContent);
}
// For authenticated media, fetch with auth header and return blob URL
if (useAuthentication && isAuthenticatedMediaUrl(mediaUrl)) {
return fetchAuthenticatedMedia(mediaUrl, accessToken);
}
return mediaUrl;
}, [mx, url, useAuthentication, mimeType, encInfo])
);