CI / Build & Quality Checks (push) Successful in 1m46s
CI / Docker image build & smoke test (push) Skipped
CI / Secret scan (gitleaks) (push) Successful in 8s
CI / Trigger Desktop Build (push) Successful in 11s
CI / Playwright smoke (e2e) (push) Successful in 9m44s
A reply to an image, video or sticker used to quote just the filename. The quote (timeline) and the composer's reply-draft preview now carry a 36 px thumbnail from the event's own thumbnail, decrypted for E2EE media via the same hook the gallery uses — never the full-size file. Clicking still jumps to the original. useDecryptedMediaUrl and getThumbMxc moved out of MediaGallery into hooks/ and utils/ so components/message can use them without a cycle. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
66 lines
1.9 KiB
TypeScript
66 lines
1.9 KiB
TypeScript
import { useEffect, useRef, useState } from 'react';
|
|
import { MatrixClient } from 'matrix-js-sdk';
|
|
import { IEncryptedFile } from '../../types/matrix/common';
|
|
import { decryptFile, downloadEncryptedMedia, mxcUrlToHttp } from '../utils/matrix';
|
|
|
|
type DecryptState = { status: 'loading' } | { status: 'ok'; url: string } | { status: 'error' };
|
|
|
|
export function useDecryptedMediaUrl(
|
|
mx: MatrixClient,
|
|
mxcUrl: string | undefined,
|
|
encInfo: IEncryptedFile | undefined,
|
|
useAuthentication: boolean,
|
|
mimeType?: string,
|
|
enabled = true,
|
|
): DecryptState {
|
|
const [state, setState] = useState<DecryptState>({ status: 'loading' });
|
|
const prevBlobUrl = useRef<string | null>(null);
|
|
|
|
useEffect(() => {
|
|
if (!enabled) return undefined;
|
|
if (!mxcUrl) {
|
|
setState({ status: 'error' });
|
|
return;
|
|
}
|
|
let cancelled = false;
|
|
setState({ status: 'loading' });
|
|
|
|
const run = async () => {
|
|
const httpUrl = mxcUrlToHttp(mx, mxcUrl, useAuthentication);
|
|
if (!httpUrl) throw new Error('bad url');
|
|
if (encInfo) {
|
|
const blob = await downloadEncryptedMedia(httpUrl, (buf) =>
|
|
decryptFile(buf, mimeType ?? 'application/octet-stream', encInfo),
|
|
);
|
|
const blobUrl = URL.createObjectURL(blob);
|
|
if (cancelled) {
|
|
URL.revokeObjectURL(blobUrl);
|
|
return;
|
|
}
|
|
if (prevBlobUrl.current) URL.revokeObjectURL(prevBlobUrl.current);
|
|
prevBlobUrl.current = blobUrl;
|
|
setState({ status: 'ok', url: blobUrl });
|
|
} else {
|
|
setState({ status: 'ok', url: httpUrl });
|
|
}
|
|
};
|
|
|
|
run().catch(() => {
|
|
if (!cancelled) setState({ status: 'error' });
|
|
});
|
|
|
|
return () => {
|
|
cancelled = true;
|
|
};
|
|
}, [mx, mxcUrl, encInfo, useAuthentication, mimeType, enabled]);
|
|
|
|
useEffect(
|
|
() => () => {
|
|
if (prevBlobUrl.current) URL.revokeObjectURL(prevBlobUrl.current);
|
|
},
|
|
[],
|
|
);
|
|
|
|
return state;
|
|
}
|