fix: refactors

This commit is contained in:
David Nguyen
2026-02-25 19:26:09 +11:00
parent 653d340668
commit 8e8f57661c
16 changed files with 59 additions and 61 deletions
@@ -62,7 +62,7 @@ export default function EnvelopeEditorFieldsPageRenderer({
editorFields.localFields.filter(
(field) => field.page === pageNumber && field.envelopeItemId === currentEnvelopeItem?.id,
),
[editorFields.localFields, pageNumber],
[editorFields.localFields, pageNumber, currentEnvelopeItem?.id],
);
const handleResizeOrMove = (event: KonvaEventObject<Event>) => {
@@ -98,7 +98,7 @@ export default function EnvelopeSignerPageRenderer({ pageData }: { pageData: Pag
return fieldsToRender.filter(
(field) => field.page === pageNumber && field.envelopeItemId === currentEnvelopeItem?.id,
);
}, [recipientFields, selectedAssistantRecipientFields, pageNumber]);
}, [recipientFields, selectedAssistantRecipientFields, pageNumber, currentEnvelopeItem?.id]);
/**
* Returns fields that have been fully signed by other recipients for this specific
@@ -51,10 +51,6 @@ route.get(
return c.json({ error: 'Not found' }, 404);
}
// We can hard cache this since since it's a unique URL for a given recipient.
// Might be dicey if the handler returns a cacheable error code.
c.header('Cache-Control', 'public, max-age=31536000, immutable');
return await handleEnvelopeItemPageRequest({
c,
envelopeItem,
@@ -133,16 +133,17 @@ export const handleEnvelopeItemPageRequest = async ({
const documentDataToUse =
version === 'current' ? envelopeItem.documentData.data : envelopeItem.documentData.initialData;
c.header('Content-Type', 'image/jpeg');
c.header('Cache-Control', `${cacheStrategy}, max-age=31536000, immutable`);
// Return the image if it already exists in S3.
if (envelopeItem.documentData.type === 'S3_PATH') {
const s3Key = getEnvelopeItemPageImageS3Key(documentDataToUse, pageIndex);
const image = await UNSAFE_getS3File(s3Key);
const image = await UNSAFE_getS3File(s3Key).catch(() => null);
if (image) {
// Note: Only set these headers on success.
c.header('Content-Type', 'image/jpeg');
c.header('Cache-Control', `${cacheStrategy}, max-age=31536000, immutable`);
return c.body(image);
}
}
@@ -169,6 +170,10 @@ export const handleEnvelopeItemPageRequest = async ({
return c.json({ error: 'Failed to render page to image' }, 500);
}
// Note: Only set these headers on success.
c.header('Content-Type', 'image/jpeg');
c.header('Cache-Control', `${cacheStrategy}, max-age=31536000, immutable`);
return c.body(image);
};
@@ -110,12 +110,10 @@ export const handleEnvelopeItemsMetaRequest = async ({
const pdfPageMetadata: TDocumentDataMeta['pages'] = await extractAndStorePdfImages(
new Uint8Array(pdfBytes).buffer,
item.documentData.id,
item.documentData.type,
);
pageMetadata = {
pages: pdfPageMetadata,
documentDataType: item.documentData.type,
};
}
@@ -64,13 +64,19 @@ export const useFieldPageCoords = (
const pageSelector = `${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${field.page}"]`;
let resizeObserver: ResizeObserver | null = null;
let observedElement: HTMLElement | null = null;
const attachResizeObserver = ($page: HTMLElement) => {
if ($page === observedElement) {
return;
}
resizeObserver?.disconnect();
resizeObserver = new ResizeObserver(() => {
calculateCoords();
});
resizeObserver.observe($page);
observedElement = $page;
};
// Try to attach immediately if the page already exists.
@@ -99,6 +105,7 @@ export const useFieldPageCoords = (
return () => {
mutationObserver.disconnect();
resizeObserver?.disconnect();
observedElement = null;
};
}, [calculateCoords, field.page]);
@@ -8,7 +8,7 @@ import { EAGER_LOAD_PAGE_COUNT, type PageRenderData } from '../providers/envelop
type RenderFunction = (props: { stage: Konva.Stage; pageLayer: Konva.Layer }) => void;
export function usePageRenderer(renderFunction: RenderFunction, pageData: PageRenderData) {
export const usePageRenderer = (renderFunction: RenderFunction, pageData: PageRenderData) => {
const { pageWidth, pageHeight, scale, imageUrl, pageNumber } = pageData;
const konvaContainer = useRef<HTMLDivElement>(null);
@@ -70,7 +70,7 @@ export function usePageRenderer(renderFunction: RenderFunction, pageData: PageRe
src: imageUrl,
'data-page-number': pageNumber,
}),
[renderViewport, scaledViewport],
[renderViewport, scaledViewport, imageUrl],
);
useEffect(() => {
@@ -120,4 +120,4 @@ export function usePageRenderer(renderFunction: RenderFunction, pageData: PageRe
renderStatus,
setRenderStatus,
};
}
};
@@ -29,9 +29,6 @@ import { getEnvelopeItemMetaUrl, getEnvelopeItemPageImageUrl } from '../../utils
*/
export const EAGER_LOAD_PAGE_COUNT = 5;
// Todo: Embeds
export const PRESIGNED_ENVELOPE_ITEM_ID_PREFIX = 'PRESIGNED_';
export type PageRenderData = BasePageRenderData & {
scale: number;
};
@@ -170,7 +167,7 @@ export const EnvelopeRenderProvider = ({
const fetchStartedAtRef = useRef<number>(0);
const envelopeItems = useMemo(
() => envelopeItemsFromProps.sort((a, b) => a.order - b.order),
() => [...envelopeItemsFromProps].sort((a, b) => a.order - b.order),
[envelopeItemsFromProps],
);
@@ -183,7 +180,7 @@ export const EnvelopeRenderProvider = ({
*/
useEffect(() => {
void fetchEnvelopeRenderData();
}, [envelope.id, envelopeItems, token, version]);
}, [envelope.id, envelopeItems, token, version, presignToken]);
const fetchEnvelopeRenderData = useCallback(async () => {
if (envelopeItems.length === 0) {
@@ -333,19 +330,6 @@ export const EnvelopeRenderProvider = ({
);
}
// Append all local embedding files.
const localFiles = envelopeItems.filter(
(item) => item.id.startsWith(PRESIGNED_ENVELOPE_ITEM_ID_PREFIX) && item.data,
);
for (const item of localFiles) {
if (!item.data) {
throw new Error('Not possible');
}
// Handle local files
}
setEnvelopeItemsMeta(metaMap);
setEnvelopeItemsMetaLoadingState('loaded');
@@ -491,11 +491,14 @@ const decorateAndSignPdf = async ({
// Add suffix based on document status
const suffix = isRejected ? '_rejected.pdf' : '_signed.pdf';
const newDocumentData = await putPdfFileServerSide({
name: `${name}${suffix}`,
type: 'application/pdf',
arrayBuffer: async () => Promise.resolve(pdfBytes),
});
const newDocumentData = await putPdfFileServerSide(
{
name: `${name}${suffix}`,
type: 'application/pdf',
arrayBuffer: async () => Promise.resolve(pdfBytes),
},
envelopeItem.documentData.initialData,
);
return {
oldDocumentDataId: envelopeItem.documentData.id,
@@ -5,14 +5,25 @@ import { prisma } from '@documenso/prisma';
export type CreateDocumentDataOptions = {
type: DocumentDataType;
data: string;
/**
* The initial data that was used to create the document data.
*
* If not provided, the current data will be used.
*/
initialData?: string;
};
export const createDocumentData = async ({ type, data }: CreateDocumentDataOptions) => {
export const createDocumentData = async ({
type,
data,
initialData,
}: CreateDocumentDataOptions) => {
return await prisma.documentData.create({
data: {
type,
data,
initialData: data,
initialData: initialData || data,
},
});
};
-2
View File
@@ -1,9 +1,7 @@
import { DocumentDataType } from '@prisma/client';
import { z } from 'zod';
export const ZDocumentDataMetaSchema = z.object({
// Could store other things such as PDF size, etc here.
documentDataType: z.nativeEnum(DocumentDataType),
pages: z
.object({
originalWidth: z.number().describe('Original PDF page width'),
@@ -26,7 +26,7 @@ type File = {
* Uploads a document file to the appropriate storage location and creates
* a document data record.
*/
export const putPdfFileServerSide = async (file: File) => {
export const putPdfFileServerSide = async (file: File, initialData?: string) => {
const isEncryptedDocumentsAllowed = false; // Was feature flag.
const arrayBuffer = await file.arrayBuffer();
@@ -47,9 +47,9 @@ export const putPdfFileServerSide = async (file: File) => {
const { type, data } = await putFileServerSide(file);
const newDocumentData = await createDocumentData({ type, data });
const newDocumentData = await createDocumentData({ type, data, initialData });
void extractAndStorePdfImages(arrayBuffer, newDocumentData.id, type).catch((err) => {
void extractAndStorePdfImages(arrayBuffer, newDocumentData.id).catch((err) => {
console.error(`Error extracting and storing PDF images: ${err}`);
// Do nothing.
@@ -64,7 +64,6 @@ export const putPdfFileServerSide = async (file: File) => {
export const extractAndStorePdfImages = async (
arrayBuffer: ArrayBuffer,
documentDataId: string,
documentDataType: DocumentDataType,
): Promise<TDocumentDataMeta['pages']> => {
const images = await pdfToImages(new Uint8Array(arrayBuffer));
@@ -78,7 +77,6 @@ export const extractAndStorePdfImages = async (
const documentDataMetadata = ZDocumentDataMetaSchema.parse({
pages: pageMetadata,
documentDataType,
} satisfies TDocumentDataMeta);
// Only update metadata (page dimensions). Never update type, data, or initialData:
@@ -142,7 +140,7 @@ export const putNormalizedPdfFileServerSide = async (
data: documentData.data,
});
void extractAndStorePdfImages(normalized, newDocumentData.id, documentData.type).catch((err) => {
void extractAndStorePdfImages(normalized, newDocumentData.id).catch((err) => {
console.error(`Error extracting and storing PDF images: ${err}`);
// Do nothing.
@@ -129,7 +129,8 @@ export const deleteS3File = async (key: string) => {
* frontend to ever pull a file from S3 directly.
*/
export const UNSAFE_getS3File = async (key: string) => {
// Additional safeguard to prevent path traversal.
// Basic safeguard to prevent path traversal.
// Key should never be user-controlled.
if (key.includes('..') || key.startsWith('/')) {
throw new Error('Invalid S3 key');
}
@@ -143,7 +144,7 @@ export const UNSAFE_getS3File = async (key: string) => {
}),
);
return response.Body;
return response.Body || null;
};
const getS3Client = () => {
+6
View File
@@ -73,5 +73,11 @@ export const getEnvelopeItemPageImageS3Key = (
const baseKey = documentDataId.split('/')[0];
// Basic safeguard to prevent path traversal.
// Key should never be user-controlled.
if (baseKey.includes('..') || baseKey.startsWith('/')) {
throw new Error('Invalid S3 key');
}
return `${baseKey}/${pageIndex}.jpeg`;
};
@@ -123,11 +123,7 @@ export const EnvelopePdfViewer = ({
{/* No current item selected */}
{envelopeItemsMetaLoadingState === 'loaded' && !currentEnvelopeItem && (
<div
className={cn(
'flex h-[80vh] max-h-[60rem] w-full flex-col items-center justify-center overflow-hidden rounded',
)}
>
<div className="flex h-[80vh] max-h-[60rem] w-full flex-col items-center justify-center overflow-hidden rounded">
<p className="text-sm text-muted-foreground">
<Trans>No document selected</Trans>
</p>
@@ -1,15 +1,10 @@
import { Trans } from '@lingui/react/macro';
import { cn } from '@documenso/ui/lib/utils';
import { Spinner } from '@documenso/ui/primitives/spinner';
export const PdfViewerLoadingState = () => {
return (
<div
className={cn(
'flex h-[80vh] max-h-[60rem] w-full flex-col items-center justify-center overflow-hidden',
)}
>
<div className="flex h-[80vh] max-h-[60rem] w-full flex-col items-center justify-center overflow-hidden">
<Spinner />
</div>
);