Improve timeline scroll, media layout, avatars, and emoji coverage.
Remember room scroll position across switches, reserve image/video heights from Matrix dimensions, cache authenticated media blobs for avatars, restore jumbo emoji-only messages, and extend emoji font unicode-range for Unicode 15 glyphs.
This commit is contained in:
@@ -1,89 +1,57 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useMatrixClient } from './useMatrixClient';
|
||||
import { getCurrentAccessToken } from '../utils/auth';
|
||||
import {
|
||||
getCachedAuthenticatedMediaUrl,
|
||||
isAuthenticatedMediaUrl,
|
||||
resolveAuthenticatedMediaUrl,
|
||||
} from '../utils/authenticatedMediaCache';
|
||||
|
||||
/**
|
||||
* Fetches media with authentication and returns a blob URL.
|
||||
* This is needed because service workers may not work reliably in Tauri/WebKit environments,
|
||||
* and cross-origin image requests cannot include Authorization headers.
|
||||
* Resolves media URLs for Matrix authenticated media.
|
||||
* Uses a shared blob-URL cache so avatars/thumbs are not re-fetched on every remount.
|
||||
*/
|
||||
export const useAuthenticatedMediaUrl = (
|
||||
src: string | undefined,
|
||||
useAuthentication: boolean
|
||||
): string | undefined => {
|
||||
const mx = useMatrixClient();
|
||||
const [blobUrl, setBlobUrl] = useState<string | undefined>(undefined);
|
||||
const [blobUrl, setBlobUrl] = useState<string | undefined>(() => {
|
||||
if (!src) return undefined;
|
||||
if (!useAuthentication || !isAuthenticatedMediaUrl(src)) return src;
|
||||
return getCachedAuthenticatedMediaUrl(src);
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!src) {
|
||||
setBlobUrl(undefined);
|
||||
return;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// If not using authentication, just return the original URL
|
||||
if (!useAuthentication) {
|
||||
if (!useAuthentication || !isAuthenticatedMediaUrl(src)) {
|
||||
setBlobUrl(src);
|
||||
return;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Check if this is an authenticated media URL
|
||||
const isAuthenticatedMediaUrl =
|
||||
src.includes('/_matrix/client/v1/media/download') ||
|
||||
src.includes('/_matrix/client/v1/media/thumbnail') ||
|
||||
(src.includes('/_matrix/media/') &&
|
||||
(src.includes('/download/') || src.includes('/thumbnail/')));
|
||||
|
||||
if (!isAuthenticatedMediaUrl) {
|
||||
setBlobUrl(src);
|
||||
return;
|
||||
const cached = getCachedAuthenticatedMediaUrl(src);
|
||||
if (cached) {
|
||||
setBlobUrl(cached);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let cancelled = false;
|
||||
let objectUrl: string | undefined;
|
||||
|
||||
const fetchMedia = async () => {
|
||||
try {
|
||||
// Always use current session's token to avoid stale tokens during account switches
|
||||
const accessToken = getCurrentAccessToken();
|
||||
let response = await fetch(src, {
|
||||
method: 'GET',
|
||||
headers: accessToken
|
||||
? { Authorization: `Bearer ${accessToken}` }
|
||||
: undefined,
|
||||
});
|
||||
|
||||
// If we got a 401 and we tried with auth, fallback to unauthenticated request
|
||||
if (!response.ok && response.status === 401 && accessToken) {
|
||||
console.warn('[useAuthenticatedMediaUrl] Auth failed (401), attempting unauthenticated fallback for:', src);
|
||||
response = await fetch(src, { method: 'GET' });
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
console.warn(`Failed to fetch authenticated media: ${response.status}`);
|
||||
// Fall back to original URL in case server doesn't require auth
|
||||
if (!cancelled) setBlobUrl(src);
|
||||
return;
|
||||
}
|
||||
|
||||
const blob = await response.blob();
|
||||
if (!cancelled) {
|
||||
objectUrl = URL.createObjectURL(blob);
|
||||
setBlobUrl(objectUrl);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Error fetching authenticated media:', error);
|
||||
// Fall back to original URL
|
||||
resolveAuthenticatedMediaUrl(src, useAuthentication)
|
||||
.then((url) => {
|
||||
if (!cancelled) setBlobUrl(url);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn('[useAuthenticatedMediaUrl] Error fetching authenticated media:', error);
|
||||
if (!cancelled) setBlobUrl(src);
|
||||
}
|
||||
};
|
||||
|
||||
fetchMedia();
|
||||
});
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
if (objectUrl) {
|
||||
URL.revokeObjectURL(objectUrl);
|
||||
}
|
||||
// Shared cache owns blob URLs — do not revoke on unmount.
|
||||
};
|
||||
}, [src, useAuthentication, mx]);
|
||||
|
||||
@@ -98,42 +66,11 @@ export const useAuthenticatedMediaFetch = () => {
|
||||
const mx = useMatrixClient();
|
||||
|
||||
return useCallback(
|
||||
async (src: string): Promise<string> => {
|
||||
const isAuthenticatedMediaUrl =
|
||||
src.includes('/_matrix/client/v1/media/download') ||
|
||||
src.includes('/_matrix/client/v1/media/thumbnail') ||
|
||||
(src.includes('/_matrix/media/') &&
|
||||
(src.includes('/download/') || src.includes('/thumbnail/')));
|
||||
|
||||
if (!isAuthenticatedMediaUrl) {
|
||||
return src;
|
||||
}
|
||||
|
||||
async (src: string, useAuthentication = true): Promise<string> => {
|
||||
try {
|
||||
// Always use current session's token to avoid stale tokens during account switches
|
||||
const accessToken = getCurrentAccessToken();
|
||||
let response = await fetch(src, {
|
||||
method: 'GET',
|
||||
headers: accessToken
|
||||
? { Authorization: `Bearer ${accessToken}` }
|
||||
: undefined,
|
||||
});
|
||||
|
||||
// If we got a 401 and we tried with auth, fallback to unauthenticated request
|
||||
if (!response.ok && response.status === 401 && accessToken) {
|
||||
console.warn('[useAuthenticatedMediaFetch] Auth failed (401), attempting unauthenticated fallback');
|
||||
response = await fetch(src, { method: 'GET' });
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
console.warn(`Failed to fetch authenticated media: ${response.status}`);
|
||||
return src;
|
||||
}
|
||||
|
||||
const blob = await response.blob();
|
||||
return URL.createObjectURL(blob);
|
||||
return await resolveAuthenticatedMediaUrl(src, useAuthentication);
|
||||
} catch (error) {
|
||||
console.warn('Error fetching authenticated media:', error);
|
||||
console.warn('[useAuthenticatedMediaFetch] Error fetching authenticated media:', error);
|
||||
return src;
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user