fix: GIF CSP + edit history HTML rendering + unhandled rejection cleanup
- nginx (LXC 106, live): added https://*.giphy.com to connect-src CSP — browser was blocking fetch() to media2.giphy.com CDN with CSP violation - EditHistoryModal: render formatted_body as sanitized HTML (via html-react-parser + sanitizeCustomHtml) with linkification for plain text, matching how messages render in the timeline - useAsyncCallback + ThumbnailContent + ImageContent + VideoContent + ClientConfigLoader: use .catch(() => undefined) instead of void to silence unhandled promise rejections from fire-and-forget useEffect calls — errors already captured in AsyncState.Error for UI display Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -21,7 +21,7 @@ export function ClientConfigLoader({ fallback, error, children }: ClientConfigLo
|
||||
const ignoreCallback = useCallback(() => setIgnoreError(true), []);
|
||||
|
||||
useEffect(() => {
|
||||
void load().catch(() => undefined);
|
||||
load().catch(() => undefined);
|
||||
}, [load]);
|
||||
|
||||
if (state.status === AsyncStatus.Idle || state.status === AsyncStatus.Loading) {
|
||||
|
||||
@@ -113,7 +113,7 @@ export const ImageContent = as<'div', ImageContentProps>(
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (autoPlay) void loadSrc().catch(() => undefined);
|
||||
if (autoPlay) loadSrc().catch(() => undefined);
|
||||
}, [autoPlay, loadSrc]);
|
||||
|
||||
return (
|
||||
|
||||
@@ -37,7 +37,7 @@ export function ThumbnailContent({ info, renderImage }: ThumbnailContentProps) {
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
void loadThumbSrc().catch(() => undefined);
|
||||
loadThumbSrc().catch(() => undefined);
|
||||
}, [loadThumbSrc]);
|
||||
|
||||
return thumbSrcState.status === AsyncStatus.Success ? renderImage(thumbSrcState.data) : null;
|
||||
|
||||
@@ -106,7 +106,7 @@ export const VideoContent = as<'div', VideoContentProps>(
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (autoPlay) void loadSrc().catch(() => undefined);
|
||||
if (autoPlay) loadSrc().catch(() => undefined);
|
||||
}, [autoPlay, loadSrc]);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import React, { ReactNode, useCallback, useEffect } from 'react';
|
||||
import parse from 'html-react-parser';
|
||||
import Linkify from 'linkify-react';
|
||||
import FocusTrap from 'focus-trap-react';
|
||||
import {
|
||||
Box,
|
||||
@@ -18,6 +20,8 @@ import {
|
||||
import { MatrixEvent, Room } from 'matrix-js-sdk';
|
||||
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
||||
import { stopPropagation } from '../../../utils/keyboard';
|
||||
import { sanitizeCustomHtml } from '../../../utils/sanitize';
|
||||
import { LINKIFY_OPTS } from '../../../plugins/react-custom-html-parser';
|
||||
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
|
||||
import { timeDayMonYear, timeHourMinute } from '../../../utils/time';
|
||||
import { useSetting } from '../../../state/hooks/settings';
|
||||
@@ -52,17 +56,24 @@ function isRawEditEvent(raw: unknown): raw is RawEditEvent {
|
||||
return typeof r.event_id === 'string' && typeof r.origin_server_ts === 'number';
|
||||
}
|
||||
|
||||
function getVersionBody(evt: MatrixEvent): string {
|
||||
function getVersionContent(evt: MatrixEvent): ReactNode {
|
||||
const content = evt.getContent();
|
||||
const newContent = content['m.new_content'] as Record<string, unknown> | undefined;
|
||||
const source = newContent ?? content;
|
||||
|
||||
const format = source.format;
|
||||
const formattedBody = source.formatted_body;
|
||||
if (typeof formattedBody === 'string') {
|
||||
return formattedBody.replace(/<[^>]+>/g, '').trim() || '(no text)';
|
||||
if (
|
||||
format === 'org.matrix.custom.html' &&
|
||||
typeof formattedBody === 'string' &&
|
||||
formattedBody.trim()
|
||||
) {
|
||||
return parse(sanitizeCustomHtml(formattedBody));
|
||||
}
|
||||
|
||||
const body = source.body;
|
||||
return typeof body === 'string' ? body : '(no text)';
|
||||
const text = typeof body === 'string' ? body : '(no text)';
|
||||
return <Linkify options={LINKIFY_OPTS}>{text}</Linkify>;
|
||||
}
|
||||
|
||||
export function EditHistoryModal({ room, mEvent, onClose }: EditHistoryModalProps) {
|
||||
@@ -106,7 +117,7 @@ export function EditHistoryModal({ room, mEvent, onClose }: EditHistoryModalProp
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
void fetchHistory().catch(() => undefined);
|
||||
fetchHistory().catch(() => undefined);
|
||||
}, [fetchHistory]);
|
||||
|
||||
const formatTs = (ts: number): string => {
|
||||
@@ -115,11 +126,7 @@ export function EditHistoryModal({ room, mEvent, onClose }: EditHistoryModalProp
|
||||
return `${date} at ${time}`;
|
||||
};
|
||||
|
||||
const originalBody = (() => {
|
||||
const content = mEvent.getContent();
|
||||
const body = content.body;
|
||||
return typeof body === 'string' ? body : '(no text)';
|
||||
})();
|
||||
const originalContent = getVersionContent(mEvent);
|
||||
|
||||
const originalTs = mEvent.getTs();
|
||||
|
||||
@@ -189,7 +196,7 @@ export function EditHistoryModal({ room, mEvent, onClose }: EditHistoryModalProp
|
||||
</Text>
|
||||
</Box>
|
||||
<Text size="T300" style={{ whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>
|
||||
{originalBody}
|
||||
{originalContent}
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
@@ -209,7 +216,7 @@ export function EditHistoryModal({ room, mEvent, onClose }: EditHistoryModalProp
|
||||
size="T300"
|
||||
style={{ whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}
|
||||
>
|
||||
{getVersionBody(editEvt)}
|
||||
{getVersionContent(editEvt)}
|
||||
</Text>
|
||||
</Box>
|
||||
))}
|
||||
|
||||
@@ -114,7 +114,7 @@ export const useAsyncCallbackValue = <TData, TError>(
|
||||
const [state, load] = useAsyncCallback<TData, TError, []>(asyncCallback);
|
||||
|
||||
useEffect(() => {
|
||||
void load().catch(() => undefined);
|
||||
load().catch(() => undefined);
|
||||
}, [load]);
|
||||
|
||||
return [state, load];
|
||||
|
||||
Reference in New Issue
Block a user