mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-28 14:50:57 +00:00
80 lines
2.4 KiB
TypeScript
80 lines
2.4 KiB
TypeScript
'use client';
|
|
|
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
|
|
import { supportApi } from './supportApi';
|
|
|
|
/**
|
|
* Blob object URL for an attachment, or `undefined` while it loads / on failure.
|
|
*
|
|
* Chat attachments cannot be rendered with a direct `<img src={a.url}>`. The API
|
|
* guard takes the bearer token from the `Authorization` header and has no cookie
|
|
* fallback, and an `<img>` request cannot carry that header — a direct src is an
|
|
* unavoidable 401. So the bytes are fetched through the authenticated client and
|
|
* handed to the browser as an object URL.
|
|
*
|
|
* The URL is revoked on unmount and whenever the attachment changes, so a thread
|
|
* scrolled through hundreds of images doesn't pin all of them in memory.
|
|
*/
|
|
export function useAttachmentObjectUrl(relativeUrl: string): {
|
|
src?: string;
|
|
failed: boolean;
|
|
} {
|
|
const [src, setSrc] = useState<string>();
|
|
const [failed, setFailed] = useState(false);
|
|
|
|
useEffect(() => {
|
|
let cancelled = false;
|
|
let created: string | undefined;
|
|
|
|
setSrc(undefined);
|
|
setFailed(false);
|
|
|
|
supportApi
|
|
.fetchAttachment(relativeUrl)
|
|
.then((blob) => {
|
|
// The component may have unmounted mid-flight; creating a URL then would
|
|
// leak it, since the cleanup below has already run.
|
|
if (cancelled) return;
|
|
created = URL.createObjectURL(blob);
|
|
setSrc(created);
|
|
})
|
|
.catch(() => {
|
|
if (!cancelled) setFailed(true);
|
|
});
|
|
|
|
return () => {
|
|
cancelled = true;
|
|
if (created) URL.revokeObjectURL(created);
|
|
};
|
|
}, [relativeUrl]);
|
|
|
|
return { src, failed };
|
|
}
|
|
|
|
/**
|
|
* On-demand variant for files that aren't previewed inline (documents): fetch
|
|
* only when the user actually opens one, rather than pulling every attachment in
|
|
* the thread down just to render a filename row.
|
|
*
|
|
* Holds a single slot — opening another file revokes the previous URL, as does
|
|
* unmounting.
|
|
*/
|
|
export function useLazyAttachmentObjectUrl(): (relativeUrl: string) => Promise<string> {
|
|
const current = useRef<string>();
|
|
|
|
useEffect(
|
|
() => () => {
|
|
if (current.current) URL.revokeObjectURL(current.current);
|
|
},
|
|
[],
|
|
);
|
|
|
|
return useCallback(async (relativeUrl: string) => {
|
|
const blob = await supportApi.fetchAttachment(relativeUrl);
|
|
if (current.current) URL.revokeObjectURL(current.current);
|
|
current.current = URL.createObjectURL(blob);
|
|
return current.current;
|
|
}, []);
|
|
}
|