mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 13:28:11 +00:00
feat: setup the attachment to the passenger clients
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
'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;
|
||||
}, []);
|
||||
}
|
||||
Reference in New Issue
Block a user