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 e18e089043
commit da35278b30
+45 -16
View File
@@ -211,36 +211,65 @@ export function MediaGallery({ room, onClose }: MediaGalleryProps) {
>
{events.map((mEvent) => {
const content = mEvent.getContent();
const isEncrypted = !!content.file;
const mxcUrl: string | undefined = content.url ?? content.file?.url;
if (!mxcUrl) return null;
const thumbUrl =
mxcUrlToHttp(mx, mxcUrl, useAuthentication, 120, 120, 'crop') ?? '';
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 (
<a
key={mEvent.getId()}
href={mxcUrlToHttp(mx, mxcUrl, useAuthentication) ?? '#'}
target="_blank"
href={isEncrypted ? '#' : fullUrl}
target={isEncrypted ? undefined : '_blank'}
rel="noreferrer"
style={{
display: 'block',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
aspectRatio: '1',
overflow: 'hidden',
borderRadius: config.radii.R300,
background: 'var(--bg-surface)',
background: 'var(--bg-surface-low)',
cursor: isEncrypted ? 'default' : 'pointer',
}}
title={body}
>
<img
src={thumbUrl}
alt={body}
style={{
width: '100%',
height: '100%',
objectFit: 'cover',
display: 'block',
}}
/>
{thumbUrl ? (
<img
src={thumbUrl}
alt={body}
onError={(e) => {
(e.currentTarget as HTMLImageElement).style.display = 'none';
}}
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>
);
})}