fix: media gallery thumbnails — skip auth URL, handle encrypted media

- Use useAuthentication=false for thumbnail requests: the v1 authenticated
  URL adds allow_redirect=true which Synapse rejects with 400
- Encrypted events (content.file set) show a lock+filename placeholder
  since server can't thumbnail encrypted blobs
- Unencrypted thumbnails add onError handler to hide broken images gracefully

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-03 01:13:18 -04:00
parent c72cd7fef3
commit 69249d1746
+45 -16
View File
@@ -211,36 +211,65 @@ export function MediaGallery({ room, onClose }: MediaGalleryProps) {
> >
{events.map((mEvent) => { {events.map((mEvent) => {
const content = mEvent.getContent(); const content = mEvent.getContent();
const isEncrypted = !!content.file;
const mxcUrl: string | undefined = content.url ?? content.file?.url; const mxcUrl: string | undefined = content.url ?? content.file?.url;
if (!mxcUrl) return null; if (!mxcUrl) return null;
const thumbUrl =
mxcUrlToHttp(mx, mxcUrl, useAuthentication, 120, 120, 'crop') ?? '';
const body: string = content.body ?? ''; const body: string = content.body ?? '';
// Use unauthenticated thumbnail URL — the v1 authenticated endpoint adds
// allow_redirect=true which Synapse rejects with 400.
const thumbUrl = isEncrypted
? null
: (mxcUrlToHttp(mx, mxcUrl, false, 120, 120, 'crop') ?? null);
const fullUrl = mxcUrlToHttp(mx, mxcUrl, useAuthentication) ?? '#';
return ( return (
<a <a
key={mEvent.getId()} key={mEvent.getId()}
href={mxcUrlToHttp(mx, mxcUrl, useAuthentication) ?? '#'} href={isEncrypted ? '#' : fullUrl}
target="_blank" target={isEncrypted ? undefined : '_blank'}
rel="noreferrer" rel="noreferrer"
style={{ style={{
display: 'block', display: 'flex',
alignItems: 'center',
justifyContent: 'center',
aspectRatio: '1', aspectRatio: '1',
overflow: 'hidden', overflow: 'hidden',
borderRadius: config.radii.R300, borderRadius: config.radii.R300,
background: 'var(--bg-surface)', background: 'var(--bg-surface-low)',
cursor: isEncrypted ? 'default' : 'pointer',
}} }}
title={body} title={body}
> >
<img {thumbUrl ? (
src={thumbUrl} <img
alt={body} src={thumbUrl}
style={{ alt={body}
width: '100%', onError={(e) => {
height: '100%', (e.currentTarget as HTMLImageElement).style.display = 'none';
objectFit: 'cover', }}
display: 'block', style={{
}} width: '100%',
/> height: '100%',
objectFit: 'cover',
display: 'block',
}}
/>
) : (
<Box
direction="Column"
alignItems="Center"
gap="100"
style={{ padding: config.space.S100 }}
>
<Icon src={isEncrypted ? Icons.Lock : Icons.Photo} size="400" />
<Text
size="T200"
truncate
style={{ maxWidth: '100%', textAlign: 'center', opacity: 0.7 }}
>
{body || (isEncrypted ? 'Encrypted' : 'Image')}
</Text>
</Box>
)}
</a> </a>
); );
})} })}